Comunidad orientada al desarrollo de videojuegos

Crear Interfaz de Usuario con Wave Engine

OBJETIVOS

Todos los juegos tienen algún tipo de interfaz para interactuar con el usuario. Estas interfaces se usan normalmente para hacer cambios en la configuración del juego, permitir cierto grado de interacción en el juego, etc.

Estos elementos no se posicionan en el mundo 3D, si no en la superficie de la pantalla. Son elementos 2D básicos con menos complejidad que los objetos 3D típicos que nos encontramos en los juegos.

Con este ejemplo nos introduciremos en los controles UI (User-Interface) de Wave Engine y veremos como podemos crear un ejemplo sencillo y rápido de interfaz como la siguiente:

screenfinalui

CREAR UN PROYECTO

Crear un nuevo proyecto de WaveEngine desde la plantilla Wave Engine y renómbralo como UISample:

02createproject

En MySecene.cs añadimos las variables de Color que utilizaremos en el ejemplo:

private Color backgroundColor = new Color(163 / 255f, 178 / 255f, 97 / 255f);
private Color darkColor = new Color(120 / 255f, 39 / 255f, 72 / 255f);
private Color lightColor = new Color(157 / 255f, 73 / 255f, 133 / 255f);

Modificamos el método CreateScene() con el código siguiente, en el que añadimos una camara fija y asignamos el color de fondo:

FixedCamera camera = new FixedCamera("view", new Vector3(2f, 0f, 2.8f), new Vector3(.5f,0,0));
RenderManager.SetActiveCamera(camera.Entity);
EntityManager.Add(camera);

Nota: No te olvides de añadir los usings WaveEngine.Components.Cameras y WaveEngine.Common.Math.

Añadiremos un cubo básico a la escena para ver como podemos manipularlo con los controles UI de Wave Engine. Primero añadimos la textura que vamos a usar en nuestro cubo a la carpeta Content de nuestro proyecto:

screensolutionui

Puedes encontrar crate.wpk en la carpeta Resources del proyecto completo que te puedes descargar aquí.

No te olvides de poner en las propiedades del archivo, en la propiedad copiar en el directorio de resultados, la opción copiar siempre o copiar si es posterior. Si no te saltará un error de FileNotFoundException.

Añadimos los siguientes usings que necesitaremos:

using WaveEngine.Framework.Graphics;
using WaveEngine.Components.Graphics3D;
using WaveEngine.Materials;

Como vamos a necesitar acceso a algunas propiedades de nuestro cubo, vamos crear estas variables de clase antes de método CreateScene():

Transform3D cubeTransform;
BasicMaterial cubeMaterial;

protected override void CreateScene()
{
    RenderManager.BackgroundColor = backgroundColor;
    ...
}
  • cubeTransform nos servirá para acceder a propiedades del cubo como LocalWorld, position, rotation o scale.
  • cubeMaterial nos servirá para acceder a propiedades de la textura como su alpha, color, etc

Ahora añadimos una función en la que crearemos nuestro cubo y lo añadiremos a la escena:

private void CreateCube3D()
{
     Entity cube = new Entity("Cube")
          .AddComponent(new Transform3D())
          .AddComponent(Model.CreateCube())
          .AddComponent(new MaterialsMap(new BasicMaterial("Content/crate.wpk")))
          .AddComponent(new ModelRenderer());

    this.cubeTransform = cube.FindComponent<Transform3D>();

    this.cubeMaterial = (BasicMaterial)cube.FindComponent<MaterialsMap>().DefaultMaterial;

    EntityManager.Add(cube);
}

con estas líneas estamos inicializando las variables cubeTransform y cubeMaterial. Una cosa importante es que no estamos posicionando el cubo en el medio de la pantalla, le estamos dando una posición un poco movida a la izquierda. Esto es porque vamos a usar la zona de la derecha de la pantalla para mostrar algunos controles UI.

Añadimos la línea de llamada al método que acabamos de crear dentro de CreateScene():

CreateCube3D();

Así que si le damos a iniciar veremos lo siguiente:

screencubeui

AÑADIR CONTROLES UI

Vamos a empezar nuestro interfaz de usuario añadiendo un slider a la izquierda, que servirá para modificar la rotación de nuestro cubo en el eje X. Primero, añadimos los usings referentes a los controles UI de Wave Engine:

using WaveEngine.Components.UI;
using WaveEngine.Framework.UI;

Definimos un nuevo método llamado CreateUI() que usaremos para añadir todo el código referente a los componentes UI:

private void CreateUI()
{
}

y añadimos la llamada correspondiente después del método CreateCube3D().

SLIDERS DE ROTACIÓN

Añadiremos un TextBlock y un Slider en el método CreateUI() de esta manera:

TextBlock t_rotX = new TextBlock()
{
     Text = "Rot X",
     VerticalAlignment = VerticalAlignment.Bottom,
     Margin = new Thickness(15, 0, 0, 60),
 };

EntityManager.Add(t_rotX);

Slider sliderRotX = new Slider()
{
     Margin = new Thickness(50, 0, 0, 0),
     Orientation = Orientation.Vertical,
     VerticalAlignment = VerticalAlignment.Center,
     Height = 360,
     Width = 40,
     Minimum = 0,
     Maximum = 360,
     Foreground = darkColor,
     Background = lightColor,
};

EntityManager.Add(sliderRotX);

El TextBlock añade un texto con una pequeña descripción sobre la funcionalidad del slider.

Al establecer la propiedad Margin estamos dando al slider la posición respecto a la esquina superior izquierda de la pantalla, el punto origen (0, 0). Por defecto, un slider tiene la orientación Horizontal pero para este necesitamos darle una orientación vertical. Con VerticalAligment.Center hacemos que el slider se centre verticalmente. Deberíamos poner HorizontalAligment a la izquierda, pero este es el valor por defecto, por eso el slider se sitúa centrado a la izquierda de la pantalla. Como vamos a rotar el cubo en el eje X, ponemos las propiedades Minimum y Maximum a 0 y 360 respectivamente.

Si le damos a iniciar podremos ver algo parecido a esto:

screensliderrotx1ui

Como queremos modificar la rotación del cubo en el eje X cuando movamos el slider necesitamos subscribirnos al evento RealTimeValueChanged, entonces después de la línea EntityManager.Add(sliderRotX) añadimos este código:

sliderRotX.RealTimeValueChanged += (s, o) =>
        {
              this.cubeTransform.Rotation.X = MathHelper.ToRadians(o.NewValue);
        };

Si iniciamos ya podemos mover el slider y vemos como rota el cubo en el eje X:

screensliderrotxui

Ahora añadiremos otro slider para rotar el cubo en el eje Y del mismo modo. Después de subscribirnos al evento anterior añadimos el siguiente código para crear otro TextBlock y otro Slider de un modo muy similar al que hemos hecho con el slider X:

TextBlock t_rotY = new TextBlock()
{
    Text = "Rot Y",
    VerticalAlignment = VerticalAlignment.Bottom,
    Margin = new Thickness(40, 0, 0, 40),

};
EntityManager.Add(t_rotY);

Slider sliderRotY = new Slider()
{
    Margin = new Thickness(100, 20, 0, 32),
    VerticalAlignment = VerticalAlignment.Bottom,
    Width = 360,
    Height = 40,
    Minimum = 0,
    Maximum = 360,
    Foreground = darkColor,
    Background = lightColor,
};
sliderRotY.RealTimeValueChanged += (s, o) =>
{
    this.cubeTransform.Rotation.Y = MathHelper.ToRadians(o.NewValue);
};

EntityManager.Add(sliderRotY);

Ahora nos aparecerá el nuevo slider con la posibilidad de rotar nuestro cubo en el eje Y. si ejecutamos veremos algo parecido a esto:

screensliderrotyui

GRID

El grid de los controles UI en Wave Engine es un panel de diseño que nos permite organizar los controles en una estructura de cuadricula de filas y columnas. esto facilita el proceso para colocar los controles en la pantalla y lo vamos a utilizar para agregar los controles adecuados que vimos al comienzo del post.

Necesitaremos acceder al alto y ancho de la pantalla para dar el tamaño al grid. Estas propiedades están alojadas en el using WaveEngine.Framework.Services.

Pon el siguiente código al final de CreateScene():

Grid grid = new Grid()
{
     HorizontalAlignment = HorizontalAlignment.Right,
     Height = WaveServices.Platform.ScreenHeight,
};

Estamos definiendo un grid alineado a la derecha de la pantalla con la misma altura que esta.

Ahora necesitamos definir las filas y las columnas de nuestro grid, entonces añadimos este código después de inicializar el grid:

grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Proportional) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(4, GridUnitType.Proportional) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Proportional) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Proportional) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Proportional) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Proportional) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(3, GridUnitType.Proportional) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(200, GridUnitType.Pixel) });

EntityManager.Add(grid);

Hemos definido un grid con 7 filas, cada una con la misma altura y una columna.

Vamos a crear un TextBlock que añadiremos a la primera fila:

TextBlock t_colors = new TextBlock()
{
      Text = "Colors",
      VerticalAlignment = VerticalAlignment.Bottom,
      Margin = new Thickness(10),
};

t_colors.SetValue(GridControl.RowProperty, 0);
t_colors.SetValue(GridControl.ColumnProperty, 0);
grid.Add(t_colors);

Si ejecutamos veremos algo parecido a esto:

screentextcolorui

STACKPANEL Y RADIOBUTTONS

Ahora añadiremos un StackPanel con 4 RadioButtons que nos permitirán cambiar el color del cubo. El código necesario para crear el stack panel y los radio buttons es:

StackPanel stackPanel = new StackPanel()
{
     Margin = new Thickness(30, 0, 0, 0),
};

stackPanel.SetValue(GridControl.RowProperty, 1);
stackPanel.SetValue(GridControl.ColumnProperty, 0
grid.Add(stackPanel);

RadioButton radio1 = new RadioButton()
{
     Text = "Red",
     GroupName = "colors",
     Foreground = Color.Red,
};

radio1.Checked += (s, o) =>
{
     this.cubeMaterial.DiffuseColor = Color.Red;
};

stackPanel.Add(radio1);

RadioButton radio2 = new RadioButton()
{
     Text = "Green",
     GroupName = "colors",
     Foreground = Color.Green,
};

radio2.Checked += (s, o) =>
{
     this.cubeMaterial.DiffuseColor = Color.Green;
};

stackPanel.Add(radio2);

RadioButton radio3 = new RadioButton()
{
     Text = "Blue",
     GroupName = "colors",
     Foreground = Color.Blue,
};

radio3.Checked += (s, o) =>
{
     this.cubeMaterial.DiffuseColor = Color.Blue;
 };

stackPanel.Add(radio3);

RadioButton radio4 = new RadioButton()
{
     Text = "White",
     GroupName = "colors",
     Foreground = Color.White,
     IsChecked = true,
};

radio4.Checked += (s, o) =>
{
    this.cubeMaterial.DiffuseColor = Color.White;
};

stackPanel.Add(radio4);

Este código es un poco largo, pero no es muy complejo. Estamos definiendo un StackPanel con 30 pixeles de margen y lo añadimos al grid. Por defecto, si no indicamos nada cuando añadimos un control al grid, este se añade a la primera fila y columna.

Todos los RadioButtons están inicializados con la misma propiedad de GroupName; esto indica que están en el mismo grupo y solo uno de ellos puede ser seleccionado al mismo tiempo. Vemos que el botón 4 tiene la propiedad IsChecked puesta a true, esto es para que salga por defecto seleccionado este botón. Y vemos cómo estamos manejando el evento Checked para poder cambiar el color cubo.

Lo ultimo para anotar es que los 4 RadioButtons son solo añadidos al StackPanel, y no al grid, porque el StackPanel está ya añadido en el grid.

Entonces si pulsamos en iniciar veremos esto:

screenradiobuttonsui

TOGGLESWITCH

Ahora veremos un ToggleSwitch en acción para añadir o eliminar el material del cubo, dependiendo de si está On u Off. Añadimos el siguiente código al final del método CreateUI():

TextBlock t_texture = new TextBlock()
{
     Text = "Textures",
     VerticalAlignment = VerticalAlignment.Bottom,
     Margin = new Thickness(10),
};

t_texture.SetValue(GridControl.RowProperty, 2);
t_texture.SetValue(GridControl.ColumnProperty, 0);
grid.Add(t_texture);

ToggleSwitch toggleTexture = new ToggleSwitch()
{
     Margin = new Thickness(30, 0, 0, 0),
     Foreground = darkColor,
     Background = lightColor,
     IsOn = true,
};

toggleTexture.Toggled += (s, o) =>
{
     this.cubeMaterial.TextureEnabled = toggleTexture.IsOn;
};

toggleTexture.SetValue(GridControl.RowProperty, 3);
toggleTexture.SetValue(GridControl.ColumnProperty, 0);

grid.Add(toggleTexture);

Hemos añadido un TextBlock y un ToggleSwitch directamente en el grid, pero con la línea toggleTexture.SetValue estamos indicando que el control será añadido a la segunda fila del grid.

Ahora podemos activar y desactivar la textura del cubo:

screentextureui

SLIDER DE TAMAÑO

Sería interesante añadir otro slider con TextBlock para escalar el cubo. Así que ponemos el siguiente código al final del método CreateUI():

TextBlock t_scale = new TextBlock()
{
     Text = "Scale",
     VerticalAlignment = VerticalAlignment.Bottom,
     Margin = new Thickness(10),
};

t_scale.SetValue(GridControl.RowProperty, 4);
t_scale.SetValue(GridControl.ColumnProperty, 0);
grid.Add(t_scale);

Slider sliderScale = new Slider()
{
     Width = 150,
     VerticalAlignment = VerticalAlignment.Bottom,
     Margin = new Thickness(30, 0, 0, 0),
     Foreground = darkColor,
     Background = lightColor,
     Value = 50,
};

sliderScale.RealTimeValueChanged += (s, o) =>
{
     this.cubeTransform.Scale = Vector3.One / 2 + (Vector3.One * (o.NewValue / 100f));
};

sliderScale.SetValue(GridControl.RowProperty, 5);
sliderScale.SetValue(GridControl.ColumnProperty, 0);
grid.Add(sliderScale);

Con esto podemos modificar la propiedad cubeTransform.Scale, con lo que el cubo cambiará de tamaño según movamos el slider:

screenscaleui

BOTÓN RESET

Otra característica que podría ser interesante es un botón que reajustará todos los valores modificados en la interfaz, por lo que vamos a poner este código al final de CreateUI():

Button b_reset = new Button()
{
     Text = "Reset",
     Margin = new Thickness(10, 0, 0, 20),
     VerticalAlignment = VerticalAlignment.Bottom,
     Foreground = Color.White,
     BackgroundColor = lightColor,
};

b_reset.Click += (s, o) =>
{
     radio4.IsChecked = true;
     toggleTexture.IsOn = true;
     sliderScale.Value = 50;
     sliderRotX.Value = 0;
     sliderRotY.Value = 0;
};

b_reset.SetValue(GridControl.RowProperty, 6);
b_reset.SetValue(GridControl.ColumnProperty, 0);
grid.Add(b_reset);

Ahora si ejecutamos podremos resetear los valores de la interfaz a los iniciales por defecto:

screenresetui

TOGGLESWITCH PARA DEBUG

La última característica interesante que podemos añadir es ver la información de depuración de los controles UI, para ello ponemos este código al principio del método CreateScene():

RenderManager.DebugLines = true;

Vamos a añadir un toggleSwitch en la esquina superior izquierda de la pantalla con la que cambiamos en tiempo real la muestra de depuración. según este activado o desactivado. Para ello creamos un nuevo método que se llamará CreateDebugMode():

private void CreateDebugMode()
{
     ToggleSwitch debugMode = new ToggleSwitch()
     {
         OnText = "Debug On",
         OffText = "Debug Off",
         Margin = new Thickness(5),
         Width = 200,
         Foreground = darkColor,
         Background = lightColor,
         IsOn = true,
     };

     debugMode.Toggled += (s, o) =>
     {
         RenderManager.DebugLines = debugMode.IsOn;
     };

     EntityManager.Add(debugMode.Entity);
}

Y al final del método CreateScene() añadimos la llamada al método que acabamos de crear:

CreateDebugMode();

Podemos ver el modo de depuración si activamos el ToggleSwitch:

screendebugonui

DESCARGAR EL CÓDIGO

Puedes descargar el proyecto completo aquí.

FUENTES

Tutorial original en inglés: http://blog.waveengine.net

Para descargar Wave Engine: http://www.waveengine.net

Traducido por Carlos Sánchez López

, , , , , ,

Leave a Reply