pixelboyz logo
Desarrollo de Videojuegos

Tutorial de MonoGame: manejo de entradas de teclado, mouse y GamePad

image

Índice de contenido


En este capítulo, exploraremos el manejo de la entrada desde el teclado, el mouse y el gamepad en su juego MonoGame. XNA/MonoGame también tiene soporte para entradas específicas de dispositivos móviles, como pantallas táctiles y de movimiento, cubriremos estos temas en un tema posterior.

Hay un video HD de este capitulo disponible aquí.

Las capacidades de entrada de XNA eran a la vez poderosas, sencillas y un poco deficientes. Si viene de otro motor de juego o biblioteca, puede que se sorprenda al descubrir que no hay una interfaz basada en eventos lista para usar, por ejemplo. Toda la entrada en XNA se realiza mediante sondeo, si desea una capa de eventos, la crea usted mismo o utiliza una de las implementaciones de terceros existentes. Por otro lado, como está a punto de ver, las interfaces provistas son increíblemente consistentes y fáciles de aprender.

Manejo de entrada de teclado

Comencemos de inmediato con un ejemplo de código:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Text;

namespace Example1
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Vector2 position;
        Texture2D texture;

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

        protected override void Initialize()
        {
            base.Initialize();
            position = new Vector2(graphics.GraphicsDevice.Viewport.
                       Width / 2 -64, 
                                    graphics.GraphicsDevice.Viewport.
                                    Height / 2 -64);
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            texture = this.Content.Load<Texture2D>("logo128");
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            // Poll for current keyboard state
            KeyboardState state = Keyboard.GetState();
            
            // If they hit esc, exit
            if (state.IsKeyDown(Keys.Escape))
                Exit();

            // Print to debug console currently pressed keys
            System.Text.StringBuilder sb = new StringBuilder();
            foreach (var key in state.GetPressedKeys())
                sb.Append("Key: ").Append(key).Append(" pressed ");

            if (sb.Length > 0)
                System.Diagnostics.Debug.WriteLine(sb.ToString());
            else
                System.Diagnostics.Debug.WriteLine("No Keys pressed");
            
            // Move our sprite based on arrow keys being pressed:
            if (state.IsKeyDown(Keys.Right))
                position.X += 10;
            if (state.IsKeyDown(Keys.Left))
                position.X -= 10;
            if (state.IsKeyDown(Keys.Up))
                position.Y -= 10;
            if (state.IsKeyDown(Keys.Down))
                position.Y += 10;

            base.Update(gameTime);
        }

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

            spriteBatch.Begin();
            spriteBatch.Draw(texture, position);
            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}

Vamos a reutilizar el mismo ejemplo básico para todos los ejemplos de este capítulo. Simplemente dibuja un sprite centrado en la pantalla, luego manipulamos la posición en Actualizar()

En este ejemplo particular, cuando el usuario presiona las teclas, se registran en la consola de depuración:

imagen

Ahora echemos un vistazo al código específico del teclado. Todo comienza con una llamada a Keyboard.GetState(), que devuelve una estructura que contiene el estado actual del teclado, incluidas las teclas modificadoras como Control o Shift. También contiene un método llamado GetPressedKeys() que devuelve una matriz de todas las teclas que están presionadas actualmente. En este ejemplo, simplemente recorremos las teclas presionadas y las escribimos para depurar. Finalmente, sondeamos el estado presionado de las teclas de flecha y movemos nuestra posición en consecuencia.

Manejo de cambios de estado clave

Una cosa que puede notar con XNA es que simplemente está verificando el estado actual de una clave. Entonces, si se presiona una tecla o no. ¿Qué sucede si solo desea responder cuando se presiona la tecla por primera vez? Esto requiere un poco de trabajo de su parte.

    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Vector2 position;
        Texture2D texture;
        KeyboardState previousState;

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

        protected override void Initialize()
        {
            base.Initialize();
            position = new Vector2(graphics.GraphicsDevice.Viewport.
                       Width / 2 -64, 
                                    graphics.GraphicsDevice.Viewport.
                                    Height / 2 -64);

            previousState = Keyboard.GetState();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            texture = this.Content.Load<Texture2D>("logo128");
        }

        protected override void Update(GameTime gameTime)
        {
            KeyboardState state = Keyboard.GetState();
            
            // If they hit esc, exit
            if (state.IsKeyDown(Keys.Escape))
                Exit();

            // Move our sprite based on arrow keys being pressed:
            if (state.IsKeyDown(Keys.Right) & !previousState.IsKeyDown(
                Keys.Right))
                position.X += 10;
            if (state.IsKeyDown(Keys.Left) & !previousState.IsKeyDown(
                Keys.Left))
                position.X -= 10;
            if (state.IsKeyDown(Keys.Up))
                position.Y -= 10;
            if (state.IsKeyDown(Keys.Down))
                position.Y += 10;

            base.Update(gameTime);

            previousState = state;
        }

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

            spriteBatch.Begin();
            spriteBatch.Draw(texture, position);
            spriteBatch.End();
            base.Draw(gameTime);
        }
    }

Los cambios a nuestro código están resaltados. Esencialmente, si desea verificar cambios en el estado de entrada (esto también se aplica a los eventos del gamepad y del mouse), debe rastrearlos usted mismo. Se trata de mantener una copia del estado anterior, luego, en su control de entrada, verifica no solo si se presionó una tecla, sino también si se presionó en el estado anterior. Si no es así, se trata de una nueva pulsación de tecla y respondemos en consecuencia. En el ejemplo anterior, al presionar las flechas hacia la izquierda o hacia la derecha, solo respondemos a las nuevas pulsaciones de teclas, por lo que moverse hacia la izquierda o hacia la derecha requiere presionar y soltar repetidamente la tecla de la flecha.

Manejo de la entrada del mouse

A continuación, exploramos el proceso de manejo de la entrada del mouse. Notará que el proceso es casi idéntico al manejo del teclado. Una vez más, comencemos con un ejemplo de código.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Text;

namespace Example2
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Vector2 position;
        Texture2D texture;

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

        protected override void Initialize()
        {
            base.Initialize();
            position = new Vector2(graphics.GraphicsDevice.Viewport.
                       Width / 2 ,
                                    graphics.GraphicsDevice.Viewport.
                                    Height / 2 );

            
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            texture = this.Content.Load<Texture2D>("logo128");
        }

        protected override void Update(GameTime gameTime)
        {
            MouseState state = Mouse.GetState();

            // Update our sprites position to the current cursor 
            location
            position.X = state.X;
            position.Y = state.Y;

            // Check if Right Mouse Button pressed, if so, exit
            if (state.RightButton == ButtonState.Pressed)
                Exit();

            base.Update(gameTime);
        }

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

            spriteBatch.Begin();
            spriteBatch.Draw(texture, position, origin:new Vector2(64,
                             64));
            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}

Cuando ejecuta este ejemplo, la textura se moverá en relación con la ubicación del mouse. Cuando el usuario hace clic derecho, la aplicación sale. La lógica funciona casi de manera idéntica al manejo de la entrada del teclado. En cada cuadro, verifica MouseState llamando a Mouse.GetState(). Esta estructura MouseState contiene los X e Y actuales del mouse, así como el estado de los botones izquierdo, derecho y medio del mouse y la posición de la rueda de desplazamiento. Puede notar que también hay valores para XButton1 y XButton2, estos botones pueden cambiar de un dispositivo a otro, pero generalmente representan un botón de navegación hacia adelante y hacia atrás. En dispositivos que no admiten mouse, X e Y siempre serán 0, mientras que el estado de cada botón siempre se establecerá en ButtonState.Released. Si se trata de un dispositivo multitáctil, este código seguirá funcionando, aunque los valores solo reflejarán el punto de contacto principal (el primero). Discutiremos la entrada móvil con más detalle en un capítulo posterior. Al igual que con el manejo de eventos del teclado, si desea realizar un seguimiento cambios en estado de evento, tendrá que rastrearlos usted mismo.

Si agrega el siguiente código a su actualización, notará algunas cosas interesantes sobre la posición X, Y del mouse:

        protected override void Update(GameTime gameTime)
        {
            MouseState state = Mouse.GetState();

            // Update our sprites position to the current cursor 
            location
            position.X = state.X;
            position.Y = state.Y;

            System.Diagnostics.Debug.WriteLine(position.X.ToString() + 
                                   "," + position.Y.ToString());
            // Check if Right Mouse Button pressed, if so, exit
            if (state.RightButton == ButtonState.Pressed)
                Exit();

            base.Update(gameTime);
        }

Los valores X e Y son relativos al origen de la ventana. Es decir (0,0) es la esquina superior izquierda de la parte dibujable de la ventana, mientras que (ancho, alto) es la esquina inferior derecha. Sin embargo, si está en una aplicación con ventana, la ubicación del puntero del mouse continúa actualizándose, aún en relación con la esquina superior izquierda de la ventana de la aplicación.

imagen

También puede establecer la posición del cursor en el código usando la siguiente línea:

            if(state.MiddleButton == ButtonState.Pressed)
                Mouse.SetPosition(graphics.GraphicsDevice.Viewport.
                                  Width / 2,
                    graphics.GraphicsDevice.Viewport.Height / 2);
 

Este código centrará la posición del mouse en el medio de la pantalla cuando el usuario presione el botón central.

Finalmente, es común querer mostrar el cursor del mouse, esto se logra fácilmente usando:

            IsMouseVisible = true;  

Este miembro de la clase Game alterna la visibilidad del cursor del mouse del sistema.

imagen

Manejo de la entrada del gamepad

Ahora veremos el manejo de la entrada desde un gamepad o controlador de joystick. Probablemente no se sorprenda al descubrir que el proceso es notablemente consistente.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Text;

namespace Example3
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Vector2 position;
        Texture2D texture;

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

        protected override void Initialize()
        {
            base.Initialize();
            position = new Vector2(graphics.GraphicsDevice.Viewport.
                       Width / 2,
                                    graphics.GraphicsDevice.Viewport.
                                    Height / 2);
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            texture = this.Content.Load<Texture2D>("logo128");
        }

        protected override void Update(GameTime gameTime)
        {
            if (Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();

            // Check the device for Player One
            GamePadCapabilities capabilities = GamePad.GetCapabilities(
                                               PlayerIndex.One);
            
            // If there a controller attached, handle it
            if (capabilities.IsConnected)
            {
                // Get the current state of Controller1
                GamePadState state = GamePad.GetState(PlayerIndex.One);

                // You can check explicitly if a gamepad has support 
                for a certain feature
                if (capabilities.HasLeftXThumbStick)
                {
                    // Check teh direction in X axis of left analog 
                    stick
                    if (state.ThumbSticks.Left.X < -0.5f) 
                        position.X -= 10.0f;
                    if (state.ThumbSticks.Left.X > 0.5f) 
                        position.X += 10.0f;
                }

                // You can also check the controllers "type"
                if (capabilities.GamePadType == GamePadType.GamePad)
                {
                    if (state.IsButtonDown(Buttons.A))
                        Exit();
                }
            }
            base.Update(gameTime);
        }

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

            spriteBatch.Begin();
            spriteBatch.Draw(texture, position, origin: new Vector2(64,
                             64));
            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}

Cuando ejecuta este ejemplo, si hay un controlador conectado, presione hacia la izquierda o hacia la derecha en el joystick analógico para mover el sprite en consecuencia. Presionar el botón A (o presionar Escape para aquellos que no tienen un controlador) hará que el juego se cierre.

La lógica aquí es notablemente consistente con el manejo de eventos de mouse y teclado. La principal diferencia es la cantidad de controladores conectados y las capacidades de cada controlador pueden variar enormemente, por lo que el código debe responder de manera adecuada. En el ejemplo anterior, verificamos solo el primer controlador adjunto, pasando PlayerIndex a nuestra llamada GetEvents. Puede tener hasta 4 controladores conectados, y cada uno debe sondearse por separado.

Mandos compatibles

En la PC hay una plétora de dispositivos disponibles con una amplia gama de capacidades. Puede tener hasta 4 controladores diferentes adjuntos, cada uno accesible al pasar el PlayerIndex apropiado a GetEvents(). Se pueden devolver los siguientes tipos de dispositivos:

  • guitarra alternativa
  • ArcadePalo
  • Almohadilla de botón grande
  • bailepad
  • Kit de batería
  • palo de vuelo
  • Gamepad
  • Guitarra
  • Desconocido
  • Rueda

Obviamente, cada dispositivo admite un conjunto diferente de características, que se pueden sondear individualmente utilizando la estructura GamePadCapabilities devuelta por Gamepad.GetCapabilities().

Los botones en un controlador GamePad se tratan como teclas y botones del mouse, con un valor de presionado o liberado. Una vez más, si desea realizar un seguimiento de los cambios en el estado, debe codificar esta funcionalidad usted mismo. Cuando se trata de palancas analógicas, el valor devuelto es un Vector2 que representa la posición actual de la palanca. Un valor de 1,0 representa una palanca que está completamente hacia arriba o hacia la derecha, mientras que un valor de –1,0f representa una palanca que está completamente hacia la izquierda o hacia abajo. Un palo en 0,0 no está presionado.

Sin embargo, hay un pequeño desafío al tratar con controles analógicos que debe tener en cuenta. Incluso cuando no se presiona una palanca, casi nunca está en la posición (0.0f, 0.0f), los sensores a menudo arrojan fluctuaciones muy pequeñas desde el cero completo. Esto significa que si respondes directamente a la entrada sin tener en cuenta estas pequeñas variaciones, tus sprites se «moverían» mientras se supone que deben estar estacionarios. Esto se soluciona usando algo llamado zona muerta. Este es un rango de movimientos, o valores de movimiento, que se consideran demasiado pequeños para ser registrados. Puede pensar en una zona muerta como un valor que está «lo suficientemente cerca de cero para ser considerado cero».

Tienes un par de opciones con XNA/MonoGame para lidiar con zonas muertas. El valor predeterminado es IndependentAxis, que compara cada eje con la zona muerta por separado, Circular, que combina los valores X e Y antes de compararlos con la zona muerta (recomendado para controlar el uso de ambos ejes juntos, como una palanca que controla la vista 3D), y finalmente Ninguno, lo que ignora la zona muerta por completo. Por lo general, elegiría Ninguno si no le importa una zona muerta o si desea implementarla usted mismo.

           GamePadState state = GamePad.GetState(PlayerIndex.One,  
                                 GamePadDeadZone.Circular);  

Como puede ver, el manejo de entrada de XNA es algo escaso en comparación con otros motores de juegos, pero proporciona los componentes básicos para crear sistemas más complejos si es necesario. El enfoque para manejar la entrada a través de los dispositivos es notablemente consistente, lo que lo hace más fácil de usar y, con suerte, resulta en errores y comportamientos menos inesperados.

El video




Source link

Tags :
entradas,GamePad,manejo,Monogame,mouse,teclado,Tutorial

Comparte :

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *