Procedimientos de entrada para juegosInput practices for games

En este tema se describen patrones y técnicas para usar de forma eficaz los dispositivos de entrada en juegos Plataforma universal de Windows (UWP).This topic describes patterns and techniques for effectively using input devices in Universal Windows Platform (UWP) games.

Al leer este tema, aprenderá lo siguiente:By reading this topic, you'll learn:

  • Cómo hacer un seguimiento de los jugadores y de los dispositivos de entrada y navegación que están usando actualmentehow to track players and which input and navigation devices they're currently using
  • Cómo detectar las transiciones de botón (presionado a no presionado, sin presionar a presionado)how to detect button transitions (pressed-to-released, released-to-pressed)
  • Cómo detectar disposiciones de botones complejas con una sola pruebahow to detect complex button arrangements with a single test

Elección de una clase de dispositivo de entradaChoosing an input device class

Hay muchos tipos diferentes de API de entrada disponibles, como ArcadeStick, FlightSticky controlador de juegos.There are many different types of input APIs available to you, such as ArcadeStick, FlightStick, and Gamepad. ¿Cómo se decide qué API usar para el juego?How do you decide which API to use for your game?

Debe elegir cualquier API que le proporcione la entrada más adecuada para el juego.You should choose whichever API gives you the most appropriate input for your game. Por ejemplo, si va a crear un juego de plataforma 2D, probablemente solo puede usar la clase de controlador para juegos y no molestarse en la funcionalidad adicional disponible a través de otras clases.For example, if you're making a 2D platform game, you can probably just use the Gamepad class and not bother with the extra functionality available via other classes. Esto restringe el juego para admitir solo los controladores de juegos y proporcionar una interfaz coherente que funcionará en muchos controladores de interfaz de juegos diferentes sin necesidad de código adicional.This would restrict the game to supporting gamepads only and provide a consistent interface that will work across many different gamepads with no need for additional code.

Por otro lado, en el caso de simulaciones complejas de vuelos y carreras, es posible que desee enumerar todos los objetos de RawGameController como línea base para asegurarse de que admiten cualquier dispositivo de nicho que puedan tener los jugadores, incluidos los dispositivos como pedales o aceleradores independientes que siguen siendo utilizados por un solo jugador.On the other hand, for complex flight and racing simulations, you might want to enumerate all of the RawGameController objects as a baseline to make sure they support any niche device that enthusiast players might have, including devices such as separate pedals or throttle that are still used by a single player.

Desde allí, puede usar el método FromGameController de una clase de entrada, como controlador de juegos. FromGameController, para ver si cada dispositivo tiene una vista más seleccionada.From there, you can use an input class's FromGameController method, such as Gamepad.FromGameController, to see if each device has a more curated view. Por ejemplo, si el dispositivo es también un controlador de juegos, puede que desee ajustar la interfaz de usuario de asignación de botones para reflejarlo y proporcionar algunas asignaciones de botones predeterminados razonables para elegir.For example, if the device is also a Gamepad, then you might want to adjust the button mapping UI to reflect that, and provide some sensible default button mappings to choose from. (Esto contrasta con la exigencia de que el reproductor configure manualmente las entradas del controlador de juegos si solo usa RawGameController).(This is in contrast to requiring the player to manually configure the gamepad inputs if you're only using RawGameController.)

Como alternativa, puede consultar el ID. de proveedor (VID) y el ID. de producto (PID) de un RawGameController (con HardwareVendorId y HardwareProductId, respectivamente) y proporcionar las asignaciones de botones sugeridos para los dispositivos populares, a la vez que sigue siendo compatible con dispositivos desconocidos que salen en el futuro a través de asignaciones manuales por parte del reproductor.Alternatively, you can look at the vendor ID (VID) and product ID (PID) of a RawGameController (using HardwareVendorId and HardwareProductId, respectively) and provide suggested button mappings for popular devices while still remaining compatible with unknown devices that come out in the future via manual mappings by the player.

Seguimiento de los controladores conectadosKeeping track of connected controllers

Aunque cada tipo de controlador incluye una lista de controladores conectados (por ejemplo, controlador de juegos.controladores de juegos), es una buena idea mantener su propia lista de controladores.While each controller type includes a list of connected controllers (such as Gamepad.Gamepads), it is a good idea to maintain your own list of controllers. Consulte la lista de controladores de juegos para obtener más información (cada tipo de controlador tiene una sección con el mismo nombre en su propio tema).See The gamepads list for more information (each controller type has a similarly named section on its own topic).

Sin embargo, ¿qué ocurre cuando el reproductor desconecta el controlador o se conecta a uno nuevo?However, what happens when the player unplugs their controller, or plugs in a new one? Debe controlar estos eventos y actualizar la lista en consecuencia.You need to handle these events, and update your list accordingly. Consulte Agregar y quitar controladores de juegos para obtener más información (de nuevo, cada tipo de controlador tiene una sección con el mismo nombre en su propio tema).See Adding and removing gamepads for more information (again, each controller type has a similarly named section on its own topic).

Dado que los eventos agregados y quitados se generan de forma asincrónica, puede obtener resultados incorrectos cuando se trabaja con la lista de controladores.Because the added and removed events are raised asynchronously, you could get incorrect results when dealing with your list of controllers. Por lo tanto, siempre que tenga acceso a la lista de controladores, debe colocar un bloqueo en torno a él para que solo un subproceso pueda acceder a él a la vez.Therefore, anytime you access your list of controllers, you should put a lock around it so that only one thread can access it at a time. Esto puede hacerse con el Runtime de simultaneidad, en concreto la clase critical_section, en ** < PPL. h > **.This can be done with the Concurrency Runtime, specifically the critical_section class, in <ppl.h>.

Otro aspecto que hay que tener en cuenta es que la lista de controladores conectados estará inicialmente vacía y tarda dos o dos segundos en rellenarse.Another thing to think about is that the list of connected controllers will initially be empty, and takes a second or two to populate. Por lo tanto, si solo asigna el controlador de juegos actual en el método de inicio, será null.So if you only assign the current gamepad in the start method, it will be null!

Para rectificar esto, debe tener un método que "actualice" el controlador de juegos principal (en un juego de un solo jugador; los juegos multijugador requerirán soluciones más sofisticadas).To rectify this, you should have a method that "refreshes" the main gamepad (in a single-player game; multiplayer games will require more sophisticated solutions). A continuación, debe llamar a este método en los controladores de eventos del controlador agregado y del controlador quitado, o en el método de actualización.You should then call this method in both your controller added and controller removed event handlers, or in your update method.

El siguiente método simplemente devuelve el primer controlador de juegos de la lista (o nullptr si la lista está vacía).The following method simply returns the first gamepad in the list (or nullptr if the list is empty). Después, solo tiene que recordar comprobar nullptr en cualquier momento con el controlador.Then you just need to remember to check for nullptr anytime you do anything with the controller. Depende de usted si desea bloquear el juego cuando no hay ningún controlador conectado (por ejemplo, al pausar el juego) o simplemente hacer que el juego continúe, y omitir la entrada.It's up to you whether you want to block gameplay when there's no controller connected (for example, by pausing the game) or simply have gameplay continue, while ignoring input.

#include <ppl.h>

using namespace Platform::Collections;
using namespace Windows::Gaming::Input;
using namespace concurrency;

Vector<Gamepad^>^ m_myGamepads = ref new Vector<Gamepad^>();

Gamepad^ GetFirstGamepad()
{
    Gamepad^ gamepad = nullptr;
    critical_section::scoped_lock{ m_lock };

    if (m_myGamepads->Size > 0)
    {
        gamepad = m_myGamepads->GetAt(0);
    }

    return gamepad;
}

Juntos, aquí se muestra un ejemplo de cómo controlar la entrada desde un controlador de juegos:Putting it all together, here is an example of how to handle input from a gamepad:

#include <algorithm>
#include <ppl.h>

using namespace Platform::Collections;
using namespace Windows::Foundation;
using namespace Windows::Gaming::Input;
using namespace concurrency;

static Vector<Gamepad^>^ m_myGamepads = ref new Vector<Gamepad^>();
static Gamepad^          m_gamepad = nullptr;
static critical_section  m_lock{};

void Start()
{
    // Register for gamepad added and removed events.
    Gamepad::GamepadAdded += ref new EventHandler<Gamepad^>(&OnGamepadAdded);
    Gamepad::GamepadRemoved += ref new EventHandler<Gamepad^>(&OnGamepadRemoved);

    // Add connected gamepads to m_myGamepads.
    for (auto gamepad : Gamepad::Gamepads)
    {
        OnGamepadAdded(nullptr, gamepad);
    }
}

void Update()
{
    // Update the current gamepad if necessary.
    if (m_gamepad == nullptr)
    {
        auto gamepad = GetFirstGamepad();

        if (m_gamepad != gamepad)
        {
            m_gamepad = gamepad;
        }
    }

    if (m_gamepad != nullptr)
    {
        // Gather gamepad reading.
    }
}

// Get the first gamepad in the list.
Gamepad^ GetFirstGamepad()
{
    Gamepad^ gamepad = nullptr;
    critical_section::scoped_lock{ m_lock };

    if (m_myGamepads->Size > 0)
    {
        gamepad = m_myGamepads->GetAt(0);
    }

    return gamepad;
}

void OnGamepadAdded(Platform::Object^ sender, Gamepad^ args)
{
    // Check if the just-added gamepad is already in m_myGamepads; if it isn't, 
    // add it.
    critical_section::scoped_lock lock{ m_lock };
    auto it = std::find(begin(m_myGamepads), end(m_myGamepads), args);

    if (it == end(m_myGamepads))
    {
        m_myGamepads->Append(args);
    }
}

void OnGamepadRemoved(Platform::Object^ sender, Gamepad^ args)
{
    // Remove the gamepad that was just disconnected from m_myGamepads.
    unsigned int indexRemoved;
    critical_section::scoped_lock lock{ m_lock };

    if (m_myGamepads->IndexOf(args, &indexRemoved))
    {
        if (m_gamepad == m_myGamepads->GetAt(indexRemoved))
        {
            m_gamepad = nullptr;
        }

        m_myGamepads->RemoveAt(indexRemoved);
    }
}

Seguimiento de usuarios y sus dispositivosTracking users and their devices

Todos los dispositivos de entrada se asocian con un usuario para que su identidad pueda vincularse a su juego, logros, cambios de configuración y otras actividades.All input devices are associated with a User so that their identity can be linked to their gameplay, achievements, settings changes, and other activities. Los usuarios pueden iniciar sesión o cerrar sesión en y es habitual que un usuario diferente inicie sesión en un dispositivo de entrada que permanezca conectado al sistema después de que el usuario anterior haya cerrado la sesión. Cuando un usuario inicia o cierra sesión, se genera el evento IGameController. UserChanged .Users can sign in or sign out at will, and it's common for a different user to sign in on an input device that remains connected to the system after the previous user has signed out. When a user signs in or out, the IGameController.UserChanged event is raised. Puedes registrar un controlador de eventos para este evento a fin de realizar un seguimiento de los jugadores y de los dispositivos que usan.You can register an event handler for this event to keep track of players and the devices they're using.

La identidad del usuario también es la manera en que un dispositivo de entrada está asociado a su controlador de navegación de IUcorrespondiente.User identity is also the way that an input device is associated with its corresponding UI navigation controller.

Por estos motivos, se debe realizar un seguimiento de la entrada del reproductor y correlacionarla con la propiedad de usuario de la clase Device (heredada de la interfaz IGameController ).For these reasons, player input should be tracked and correlated with the User property of the device class (inherited from the IGameController interface).

En el ejemplo UserGamepadPairingUWP se muestra cómo se puede realizar un seguimiento de los usuarios y los dispositivos que están usando.The UserGamepadPairingUWP sample demonstrates how you can keep track of users and the devices they're using.

Detección de transiciones de botónDetecting button transitions

A veces quieres saber cuándo se presiona o se suelta un botón; es decir, cuándo cambia exactamente el estado del botón de no presionado a presionado o de presionado a no presionado.Sometimes you want to know when a button is first pressed or released; that is, precisely when the button state transitions from released to pressed or from pressed to released. Para determinarlo, debes recordar la lectura de dispositivo anterior y compararla con la lectura actual para ver qué ha cambiado.To determine this, you need to remember the previous device reading and compare the current reading against it to see what's changed.

En el ejemplo siguiente se muestra un enfoque básico para recordar la lectura anterior; Aquí se muestran los controladores de juegos, pero los principios son los mismos para el Stick Arcade, la rueda de carreras y los otros tipos de dispositivos de entrada.The following example demonstrates a basic approach for remembering the previous reading; gamepads are shown here, but the principles are the same for arcade stick, racing wheel, and the other input device types.

Gamepad gamepad;
GamepadReading newReading();
GamepadReading oldReading();

// Called at the start of the game.
void Game::Start()
{
    gamepad = Gamepad::Gamepads[0];
}

// Game::Loop represents one iteration of a typical game loop
void Game::Loop()
{
    // move previous newReading into oldReading before getting next newReading
    oldReading = newReading, newReading = gamepad.GetCurrentReading();

    // process device readings using buttonJustPressed/buttonJustReleased (see below)
}

Antes de realizar alguna cosa, Game::Loop mueve el valor existente de newReading (la lectura del controlador para juegos de la iteración de bucle anterior) a oldReading i, después, rellena newReading con una nueva lectura de controlador para juegos para la iteración actual.Before doing anything else, Game::Loop moves the existing value of newReading (the gamepad reading from the previous loop iteration) into oldReading, then fills newReading with a fresh gamepad reading for the current iteration. Esto te ofrece la información que necesitas para detectar transiciones de botón.This gives you the information you need to detect button transitions.

En el ejemplo siguiente se muestra un enfoque básico para detectar transiciones de botón:The following example demonstrates a basic approach for detecting button transitions:

bool ButtonJustPressed(const GamepadButtons selection)
{
    bool newSelectionPressed = (selection == (newReading.Buttons & selection));
    bool oldSelectionPressed = (selection == (oldReading.Buttons & selection));

    return newSelectionPressed && !oldSelectionPressed;
}

bool ButtonJustReleased(GamepadButtons selection)
{
    bool newSelectionReleased =
        (GamepadButtons.None == (newReading.Buttons & selection));

    bool oldSelectionReleased =
        (GamepadButtons.None == (oldReading.Buttons & selection));

    return newSelectionReleased && !oldSelectionReleased;
}

Estas dos funciones primero derivan el estado booleano de la selección del botón de newReading y oldReading , a continuación, realizan una lógica booleana para determinar si se ha producido la transición de destino.These two functions first derive the Boolean state of the button selection from newReading and oldReading, then perform Boolean logic to determine whether the target transition has occurred. Estas funciones devuelven true solo si la nueva lectura contiene el estado de destino (presionado o liberado, respectivamente) y la lectura anterior tampoco contiene el estado de destino; de lo contrario, devuelven false.These functions return true only if the new reading contains the target state (pressed or released, respectively) and the old reading does not also contain the target state; otherwise, they return false.

Detección de disposiciones de botones complejasDetecting complex button arrangements

Cada botón de un dispositivo de entrada proporciona una lectura digital que indica si está presionado (inactivo) o liberado (arriba).Each button of an input device provides a digital reading that indicates whether it's pressed (down) or released (up). Por motivos de eficacia, las lecturas de los botones no se representan como valores booleanos individuales; en su lugar, se empaquetan todas en campos de bits que se representan mediante enumeraciones específicas de dispositivo como GamepadButtons.For efficiency, button readings aren't represented as individual boolean values; instead, they're all packed into bitfields represented by device-specific enumerations such as GamepadButtons. Para leer botones específicos, se usa el enmascaramiento bit a bit para aislar los valores que te interesan.To read specific buttons, bitwise masking is used to isolate the values that you're interested in. Se presiona un botón (abajo) cuando se establece su bit correspondiente; de lo contrario, se libera (up).A button is pressed (down) when its corresponding bit is set; otherwise, it's released (up).

Recordar cómo se determinan los botones individuales para que se presionen o se liberen; Aquí se muestran los controladores de juegos, pero los principios son los mismos para el Stick Arcade, la rueda de carreras y los otros tipos de dispositivos de entrada.Recall how single buttons are determined to be pressed or released; gamepads are shown here, but the principles are the same for arcade stick, racing wheel, and the other input device types.

GamepadReading reading = gamepad.GetCurrentReading();

// Determines whether gamepad button A is pressed.
if (GamepadButtons::A == (reading.Buttons & GamepadButtons::A))
{
    // The A button is pressed.
}

// Determines whether gamepad button A is released.
if (GamepadButtons::None == (reading.Buttons & GamepadButtons::A))
{
    // The A button is released (not pressed).
}

Como puede ver, la determinación del estado de un solo botón es directa, pero a veces es posible que desee determinar si se presionan o se liberan varios botones, o si un conjunto de botones se organizan de una manera determinada — , algunos no.As you can see, determining the state of a single button is straight-forward, but sometimes you might want to determine whether multiple buttons are pressed or released, or if a set of buttons are arranged in a particular way—some pressed, some not. Probar varios botones es más complejo que probar botones individuales — , especialmente con el potencial del estado de los botones mixtos — , pero hay una fórmula simple para estas pruebas que se aplica a las pruebas de botón único y múltiple.Testing multiple buttons is more complex than testing single buttons—especially with the potential of mixed button state—but there's a simple formula for these tests that applies to single and multiple button tests alike.

En el ejemplo siguiente se determina si se presionan los botones A y B del controlador de juegos:The following example determines whether gamepad buttons A and B are both pressed:

if ((GamepadButtons::A | GamepadButtons::B) == (reading.Buttons & (GamepadButtons::A | GamepadButtons::B))
{
    // The A and B buttons are both pressed.
}

En el ejemplo siguiente se determina si se liberan los botones A y B del controlador de juegos:The following example determines whether gamepad buttons A and B are both released:

if ((GamepadButtons::None == (reading.Buttons & GamepadButtons::A | GamepadButtons::B))
{
    // The A and B buttons are both released (not pressed).
}

En el ejemplo siguiente se determina si se presiona el botón de controlador de juegos A mientras se suelta el botón B:The following example determines whether gamepad button A is pressed while button B is released:

if (GamepadButtons::A == (reading.Buttons & (GamepadButtons::A | GamepadButtons::B))
{
    // The A button is pressed and the B button is released (B is not pressed).
}

La fórmula que los cinco ejemplos tienen en común es que la disposición de los botones que se van a probar se especifica mediante la expresión de la izquierda del operador de igualdad mientras que los botones que se deben tomar en consideración se seleccionan mediante la expresión de enmascaramiento de la derecha.The formula that all five of these examples have in common is that the arrangement of buttons to be tested for is specified by the expression on the left-hand side of the equality operator while the buttons to be considered are selected by the masking expression on the right-hand side.

En el ejemplo siguiente se muestra esta fórmula más claramente rescribiendo el ejemplo anterior:The following example demonstrates this formula more clearly by rewriting the previous example:

auto buttonArrangement = GamepadButtons::A;
auto buttonSelection = (reading.Buttons & (GamepadButtons::A | GamepadButtons::B));

if (buttonArrangement == buttonSelection)
{
    // The A button is pressed and the B button is released (B is not pressed).
}

Esta fórmula puede aplicarse para probar cualquier número de botones en cualquier disposición de sus estados.This formula can be applied to test any number of buttons in any arrangement of their states.

Obtención del estado de la bateríaGet the state of the battery

Para cualquier dispositivo de juego que implementa la interfaz IGameControllerBatteryInfo , puede llamar a TryGetBatteryReport en la instancia del controlador para obtener un objeto BatteryReport que proporcione información sobre la batería en el controlador.For any game controller that implements the IGameControllerBatteryInfo interface, you can call TryGetBatteryReport on the controller instance to get a BatteryReport object that provides information about the battery in the controller. Puede obtener propiedades como la velocidad a la que se carga la batería (ChargeRateInMilliwatts), la capacidad de energía estimada de una nueva batería (DesignCapacityInMilliwattHours) y la capacidad energética totalmente cargada de la batería actual (FullChargeCapacityInMilliwattHours).You can get properties like the rate that the battery is charging (ChargeRateInMilliwatts), the estimated energy capacity of a new battery (DesignCapacityInMilliwattHours), and the fully-charged energy capacity of the current battery (FullChargeCapacityInMilliwattHours).

En el caso de los dispositivos de juego que admiten informes de batería detallados, puede obtener este y más información acerca de la batería, tal y como se detalla en obtener informaciónde la batería.For game controllers that support detailed battery reporting, you can get this and more information about the battery, as detailed in Get battery information. Sin embargo, la mayoría de los controladores de juegos no admiten ese nivel de informes de batería y, en su lugar, usan Hardware de bajo costo.However, most game controllers don't support that level of battery reporting, and instead use low-cost hardware. En el caso de estos controladores, tendrá que tener en cuenta las consideraciones siguientes:For these controllers, you'll need to keep the following considerations in mind:

  • ChargeRateInMilliwatts y DesignCapacityInMilliwattHours siempre serán null.ChargeRateInMilliwatts and DesignCapacityInMilliwattHours will always be NULL.

  • Puede obtener el porcentaje de la batería mediante el cálculo de RemainingCapacityInMilliwattHours / FullChargeCapacityInMilliwattHours.You can get the battery percentage by calculating RemainingCapacityInMilliwattHours / FullChargeCapacityInMilliwattHours. Debe omitir los valores de estas propiedades y solo tratar el porcentaje calculado.You should ignore the values of these properties and only deal with the calculated percentage.

  • El porcentaje de la viñeta anterior siempre será uno de los siguientes:The percentage from the previous bullet point will always be one of the following:

    • 100% (completo)100% (Full)
    • 70% (medio)70% (Medium)
    • 40% (bajo)40% (Low)
    • 10% (crítico)10% (Critical)

Si el código realiza alguna acción (como dibujar la interfaz de usuario) en función del porcentaje de duración de la batería restante, asegúrese de que se ajusta a los valores anteriores.If your code performs some action (like drawing UI) based on the percentage of battery life remaining, make sure that it conforms to the values above. Por ejemplo, si desea advertir al jugador de que la batería del controlador es baja, hágalo cuando llegue al 10%.For example, if you want to warn the player when the controller's battery is low, do so when it reaches 10%.

Vea tambiénSee also