Compartir a través de


Adición de entrada e interactividad al ejemplo Marble Maze

Los juegos para la Plataforma universal de Windows (UWP) se ejecutan en una amplia variedad de dispositivos, como equipos de escritorio, portátiles y tabletas. Un dispositivo puede tener una gran cantidad de mecanismos de entrada y control. En este documento se describen las prácticas clave que se deben tener en cuenta al trabajar con dispositivos de entrada y se muestra cómo Marble Maze aplica estas prácticas.

Nota:

El código de ejemplo que corresponde a este documento se encuentra en el ejemplo de juego Marble Maze de DirectX.

  Estos son algunos de los puntos clave que describe este documento para cuando trabajes con la entrada en el juego:

  • Cuando sea posible, admite varios dispositivos de entrada para permitir que el juego admite una gama más amplia de preferencias y capacidades entre tus clientes. Aunque el uso del controlador de juegos y el sensor es opcional, se recomienda encarecidamente mejorar la experiencia del jugador. Hemos diseñado las API de sensor y controlador de juegos para ayudarte a integrar estos dispositivos de entrada con mayor facilidad.

  • Para inicializar la entrada táctil, debes registrarte para eventos de ventana, como cuando el puntero está activado, liberado y movido. Para inicializar el acelerómetro, crea un Windows::D evices::Sensors::Accelerometer objeto al inicializar la aplicación. Un controlador de juego no requiere inicialización.

  • Para los juegos de un solo jugador, considera si combinar la entrada de todos los controladores posibles. De este modo, no es necesario realizar un seguimiento de qué entrada procede del controlador. O bien, simplemente realiza un seguimiento de la entrada solo desde el controlador agregado más recientemente, como hacemos en este ejemplo.

  • Procesar eventos de Windows antes de procesar dispositivos de entrada.

  • El controlador de juegos y el acelerómetro admiten sondeos. Es decir, puedes sondear los datos cuando lo necesites. Para los eventos táctiles, graba eventos táctiles en estructuras de datos que están disponibles para el código de procesamiento de entrada.

  • Considera si se normalizan los valores de entrada en un formato común. Si lo haces, puedes simplificar la forma en que otros componentes del juego interpretan la entrada, como la simulación de física, y pueden facilitar la escritura de juegos que funcionan en diferentes resoluciones de pantalla.

Dispositivos de entrada compatibles con Marble Maze

Marble Maze admite el controlador del juego, el mouse y la función táctil para seleccionar elementos de menú, y el controlador del juego, el mouse, el toque y el acelerómetro para controlar el juego. Marble Maze usa las API de Windows::Gaming::Input para sondear el controlador para la entrada. Touch permite a las aplicaciones realizar un seguimiento y responder a la entrada del dedo. Un acelerómetro es un sensor que mide la fuerza que se aplica a lo largo de los ejes X, Y y Z. Con Windows Runtime, puedes sondear el estado actual del dispositivo acelerómetro, así como recibir eventos táctiles a través del mecanismo de control de eventos de Windows Runtime.

Nota:

Este documento usa la función táctil para hacer referencia tanto a la entrada táctil como al puntero del mouse para hacer referencia a cualquier dispositivo que use eventos de puntero. Dado que la función táctil y el mouse usan eventos de puntero estándar, puedes usar cualquiera de los dispositivos para seleccionar elementos de menú y controlar el juego.

 

Nota:

El manifiesto del paquete establece Horizontalcomo la única rotación admitida para el juego para evitar que la orientación cambie al girar el dispositivo para rodar la canica. Para ver el manifiesto del paquete, abre Package.appxmanifest en el Explorador de soluciones en Visual Studio.

 

Inicialización de dispositivos de entrada

El controlador Xbox 360 no requiere inicialización. Para inicializar la entrada táctil, debes registrarte para los eventos de ventana, como cuando se activa el puntero (por ejemplo, el reproductor presiona el botón del mouse o toca la pantalla), se libera y se mueve. Para inicializar el acelerómetro, debes crear un Windows::D evices::Sensors::Accelerometer objeto al inicializar la aplicación.

En el ejemplo siguiente se muestra cómo se registra el método App::SetWindow para el Windows::UI::Core::CoreWindow::PointerPressed, Windows:::UI::Core::CoreWindow::PointerReleased, y eventos de puntero Windows::UI::Core::CoreWindow::PointerMoved. Estos eventos se registran durante la inicialización de la aplicación y antes del bucle del juego.

Estos eventos se controlan en un subproceso independiente que invoca a los controladores de eventos.

Para obtener más información sobre cómo se inicializa la aplicación, ver estructura de aplicaciones de Marble Maze.

window->PointerPressed += ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(
    this, 
    &App::OnPointerPressed);

window->PointerReleased += ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(
    this, 
    &App::OnPointerReleased);

window->PointerMoved += ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(
    this, 
    &App::OnPointerMoved);

La clase MarbleMazeMain también crea un objeto std::map para contener eventos táctiles. La clave de este objeto de mapa es un valor que identifica de forma única el puntero de entrada. Cada tecla se asigna a la distancia entre cada punto táctil y el centro de la pantalla. Marble Maze usa estos valores más adelante para calcular la cantidad por la que se inclina el laberinto.

typedef std::map<int, XMFLOAT2> TouchMap;
TouchMap        m_touches;

La clase MarbleMazeMain también contiene un objeto Accelerometer.

Windows::Devices::Sensors::Accelerometer^           m_accelerometer;

El objetoAccelerometer se inicializa en el constructor MarbleMazeMain, como se muestra en el ejemplo siguiente. El método Windows::D evices::Sensors::Accelerometer::GetDefault devuelve una instancia del acelerómetro predeterminado. Si no hay ningún acelerómetro predeterminado, Accelerometer::GetDefault devuelve nullptr.

// Returns accelerometer ref if there is one; nullptr otherwise.
m_accelerometer = Windows::Devices::Sensors::Accelerometer::GetDefault();

Puedes usar el mouse, el toque o un controlador de juego para navegar por los menús, como se indica a continuación:

  • Usa el panel direccional para cambiar el elemento de menú activo.
  • Usa la función táctil, el botón A o el botón Menú para seleccionar un elemento de menú o cerrar el menú actual, como la tabla de puntuación alta.
  • Usa el botón Menú para pausar o reanudar el juego.
  • Haz clic en un elemento de menú con el mouse para elegir esa acción.

Seguimiento de la entrada del controlador de juego

Para realizar un seguimiento de los controladores para juegos conectados actualmente al dispositivo, MarbleMazeMain define una variable miembro, m_myGamepads, que es una colección de objetos Windows::Gaming::Input::Gamepad. Esto se inicializa en el constructor de la siguiente manera:

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

for (auto gamepad : Gamepad::Gamepads)
{
    m_myGamepads->Append(gamepad);
}

Además, el constructor MarbleMazeMain registra eventos para cuando se agregan o quitan controladores para juegos:

Gamepad::GamepadAdded += 
    ref new EventHandler<Gamepad^>([=](Platform::Object^, Gamepad^ args)
{
    m_myGamepads->Append(args);
    m_currentGamepadNeedsRefresh = true;
});

Gamepad::GamepadRemoved += 
    ref new EventHandler<Gamepad ^>([=](Platform::Object^, Gamepad^ args)
{
    unsigned int indexRemoved;

    if (m_myGamepads->IndexOf(args, &indexRemoved))
    {
        m_myGamepads->RemoveAt(indexRemoved);
        m_currentGamepadNeedsRefresh = true;
    }
});

Cuando se agrega un controlador para juegos, se agrega a m_myGamepads; cuando se quita un controlador para juegos, comprobamos si el controlador para juegos está en m_myGamepads, y si es así, lo quitamos. En ambos casos, establecemos m_currentGamepadNeedsRefresh en true, lo que indica que es necesario reasignar m_gamepad.

Por último, asignamos un controlador para juegos a m_gamepad y establecemos m_currentGamepadNeedsRefresh en false:

m_gamepad = GetLastGamepad();
m_currentGamepadNeedsRefresh = false;

En el método Update, comprobamos si es necesario reasignar m_gamepad:

if (m_currentGamepadNeedsRefresh)
{
    auto mostRecentGamepad = GetLastGamepad();

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

    m_currentGamepadNeedsRefresh = false;
}

Si m_gamepad necesita reasignarse, le asignamos el controlador para juegos agregado más recientemente, usando GetLastGamepad, que se define de la siguiente manera:

Gamepad^ MarbleMaze::MarbleMazeMain::GetLastGamepad()
{
    Gamepad^ gamepad = nullptr;

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

    return gamepad;
}

Este método simplemente devuelve el último controlador para juegos de m_myGamepads.

Puedes conectar hasta cuatro controladores de juego a un dispositivo Windows 10. Para evitar tener que averiguar qué controlador es el activo, simplemente realizamos un seguimiento del controlador para juegos agregado más recientemente. Si tu juego admite más de un jugador, tienes que realizar un seguimiento de la entrada de cada jugador por separado.

El método MarbleMazeMain::Update sondea el controlador para juegos para la entrada:

if (m_gamepad != nullptr)
{
    m_oldReading = m_newReading;
    m_newReading = m_gamepad->GetCurrentReading();
}

Realizamos un seguimiento de la lectura de entrada que obtuvimos en el último fotograma con m_oldReading, y la lectura de entrada más reciente con m_newReading, que obtenemos llamando a Gamepad::GetCurrentReading. Esto devuelve un objeto GamepadReading, que contiene información sobre el estado actual del controlador para juegos.

Para comprobar si un botón se acaba de presionar o liberar, definimos MarbleMazeMain::ButtonJustPressed y MarbleMazeMain::ButtonJustReleased, que compara las lecturas de botón de este marco y el último fotograma. De este modo, podemos realizar una acción solo en el momento en que se presiona o suelta inicialmente un botón, y no cuando se mantiene presionado:

bool MarbleMaze::MarbleMazeMain::ButtonJustPressed(GamepadButtons selection)
{
    bool newSelectionPressed = (selection == (m_newReading.Buttons & selection));
    bool oldSelectionPressed = (selection == (m_oldReading.Buttons & selection));
    return newSelectionPressed && !oldSelectionPressed;
}

bool MarbleMaze::MarbleMazeMain::ButtonJustReleased(GamepadButtons selection)
{
    bool newSelectionReleased = 
        (GamepadButtons::None == (m_newReading.Buttons & selection));

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

    return newSelectionReleased && !oldSelectionReleased;
}

Las lecturas de los GamepadButtons se comparan mediante operaciones bit a bit: comprobamos si se presiona un botón mediante bit a bit y (&). Determinamos si un botón se ha presionado o liberado comparando la lectura antigua y la nueva lectura.

Con los métodos anteriores, se comprueba si se han presionado determinados botones y se realizan las acciones correspondientes que deben producirse. Por ejemplo, cuando se presiona el botón Menú (GamepadButtons::Menu), el estado del juego cambia de activo a pausado o en pausa a activo.

if (ButtonJustPressed(GamepadButtons::Menu) || m_pauseKeyPressed)
{
    m_pauseKeyPressed = false;

    if (m_gameState == GameState::InGameActive)
    {
        SetGameState(GameState::InGamePaused);
    }  
    else if (m_gameState == GameState::InGamePaused)
    {
        SetGameState(GameState::InGameActive);
    }
}

También comprobamos si el jugador presiona el botón Ver, en cuyo caso reiniciamos el juego o borramos la tabla de puntuación alta:

if (ButtonJustPressed(GamepadButtons::View) || m_homeKeyPressed)
{
    m_homeKeyPressed = false;

    if (m_gameState == GameState::InGameActive ||
        m_gameState == GameState::InGamePaused ||
        m_gameState == GameState::PreGameCountdown)
    {
        SetGameState(GameState::MainMenu);
        m_inGameStopwatchTimer.SetVisible(false);
        m_preGameCountdownTimer.SetVisible(false);
    }
    else if (m_gameState == GameState::HighScoreDisplay)
    {
        m_highScoreTable.Reset();
    }
}

Si el menú principal está activo, el elemento de menú activo cambia cuando se presiona el panel direccional hacia arriba o hacia abajo. Si el usuario elige la selección actual, el elemento de interfaz de usuario adecuado se marca como elegido.

// Handle menu navigation.
bool chooseSelection = 
    (ButtonJustPressed(GamepadButtons::A) 
    || ButtonJustPressed(GamepadButtons::Menu));

bool moveUp = ButtonJustPressed(GamepadButtons::DPadUp);
bool moveDown = ButtonJustPressed(GamepadButtons::DPadDown);

switch (m_gameState)
{
case GameState::MainMenu:
    if (chooseSelection)
    {
        m_audio.PlaySoundEffect(MenuSelectedEvent);
        if (m_startGameButton.GetSelected())
        {
            m_startGameButton.SetPressed(true);
        }
        if (m_highScoreButton.GetSelected())
        {
            m_highScoreButton.SetPressed(true);
        }
    }
    if (moveUp || moveDown)
    {
        m_startGameButton.SetSelected(!m_startGameButton.GetSelected());
        m_highScoreButton.SetSelected(!m_startGameButton.GetSelected());
        m_audio.PlaySoundEffect(MenuChangeEvent);
    }
    break;

case GameState::HighScoreDisplay:
    if (chooseSelection || anyPoints)
    {
        SetGameState(GameState::MainMenu);
    }
    break;

case GameState::PostGameResults:
    if (chooseSelection || anyPoints)
    {
        SetGameState(GameState::HighScoreDisplay);
    }
    break;

case GameState::InGamePaused:
    if (m_pausedText.IsPressed())
    {
        m_pausedText.SetPressed(false);
        SetGameState(GameState::InGameActive);
    }
    break;
}

Seguimiento de la entrada táctil y del mouse

Para la entrada táctil y del mouse, se elige un elemento de menú cuando el usuario toca o hace clic en él. En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::Update procesa la entrada del puntero para seleccionar elementos de menú. La variable miembro m_pointQueue realiza un seguimiento de las ubicaciones en las que el usuario ha tocado o hace clic en la pantalla. La forma en que Marble Maze recopila la entrada del puntero se describe con más detalle más adelante en este documento en la sección entrada de puntero de procesamiento.

// Check whether the user chose a button from the UI. 
bool anyPoints = !m_pointQueue.empty();
while (!m_pointQueue.empty())
{
    UserInterface::GetInstance().HitTest(m_pointQueue.front());
    m_pointQueue.pop();
}

El método UserInterface::HitTest determina si el punto proporcionado se encuentra en los límites de cualquier elemento de interfaz de usuario. Los elementos de la interfaz de usuario que superen esta prueba se marcan como táctiles. Este método usa la función auxiliar de PointInRect para determinar si el punto proporcionado se encuentra en los límites de cada elemento de la interfaz de usuario.

void UserInterface::HitTest(D2D1_POINT_2F point)
{
    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        if (!(*iter)->IsVisible())
            continue;

        TextButton* textButton = dynamic_cast<TextButton*>(*iter);
        if (textButton != nullptr)
        {
            D2D1_RECT_F bounds = (*iter)->GetBounds();
            textButton->SetPressed(PointInRect(point, bounds));
        }
    }
}

Actualización del estado del juego

Después del MarbleMazeMain::Update método procesa el controlador y la entrada táctil, actualiza el estado del juego si se presiona algún botón.

// Update the game state if the user chose a menu option. 
if (m_startGameButton.IsPressed())
{
    SetGameState(GameState::PreGameCountdown);
    m_startGameButton.SetPressed(false);
}
if (m_highScoreButton.IsPressed())
{
    SetGameState(GameState::HighScoreDisplay);
    m_highScoreButton.SetPressed(false);
}

Controlar la reproducción del juego

El bucle del juego y el método MarbleMazeMain::Update funcionan juntos para actualizar el estado de los objetos del juego. Si el juego acepta la entrada de varios dispositivos, puedes acumular la entrada de todos los dispositivos en un conjunto de variables para que puedas escribir código que sea más fácil de mantener. El método MarbleMazeMain::Update define un conjunto de variables que acumula el movimiento de todos los dispositivos.

float combinedTiltX = 0.0f;
float combinedTiltY = 0.0f;

El mecanismo de entrada puede variar de un dispositivo de entrada a otro. Por ejemplo, la entrada del puntero se controla mediante el modelo de control de eventos de Windows Runtime. Por el contrario, sondea los datos de entrada del controlador de juego cuando lo necesite. Se recomienda seguir siempre el mecanismo de entrada que se prescribe para un dispositivo determinado. En esta sección se describe cómo Marble Maze lee la entrada de cada dispositivo, cómo actualiza los valores de entrada combinados y cómo usa los valores de entrada combinados para actualizar el estado del juego.

Procesamiento de la entrada del puntero

Cuando trabaja con la entrada de puntero, llama al método Windows::UI::Core::CoreDispatcher::P rocessEvents para procesar eventos de ventana. Llama a este método en el bucle del juego antes de actualizar o representar la escena. Marble Maze llama a esto en el método App::Run:

while (!m_windowClosed)
{
    if (m_windowVisible)
    {
        CoreWindow::GetForCurrentThread()->
            Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

        m_main->Update();

        if (m_main->Render())
        {
            m_deviceResources->Present();
        }
    }
    else
    {
        CoreWindow::GetForCurrentThread()->
            Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
    }
}

Si la ventana está visible, pasamos CoreProcessEventsOption::P rocessAllIfPresent a ProcessEvents para procesar todos los eventos en cola y devolver inmediatamente; de lo contrario, pasamos CoreProcessEventsOption::P rocessOneAndAllPending procesar todos los eventos en cola y esperar al siguiente evento nuevo. Una vez procesados los eventos, Marble Maze representa y presenta el siguiente fotograma.

Windows Runtime llama al controlador registrado para cada evento que se produjo. El método App::SetWindow se registra para eventos y reenvía información de puntero a la clase MarbleMazeMain.

void App::OnPointerPressed(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->AddTouch(args->CurrentPoint->PointerId, args->CurrentPoint->Position);
}

void App::OnPointerReleased(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->RemoveTouch(args->CurrentPoint->PointerId);
}

void App::OnPointerMoved(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->UpdateTouch(args->CurrentPoint->PointerId, args->CurrentPoint->Position);
}

La clase MarbleMazeMain reacciona a los eventos de puntero actualizando el objeto de mapa que contiene eventos táctiles. Se llama al método MarbleMazeMain::AddTouch cuando se presiona por primera vez el puntero, por ejemplo, cuando el usuario toca inicialmente la pantalla en un dispositivo táctil. Se llama al método MarbleMazeMain::UpdateTouch cuando se mueve la posición del puntero. Se llama al método MarbleMazeMain::RemoveTouch cuando se libera el puntero, por ejemplo, cuando el usuario deja de tocar la pantalla.

void MarbleMazeMain::AddTouch(int id, Windows::Foundation::Point point)
{
    m_touches[id] = PointToTouch(point, m_deviceResources->GetLogicalSize());

    m_pointQueue.push(D2D1::Point2F(point.X, point.Y));
}

void MarbleMazeMain::UpdateTouch(int id, Windows::Foundation::Point point)
{
    if (m_touches.find(id) != m_touches.end())
        m_touches[id] = PointToTouch(point, m_deviceResources->GetLogicalSize());
}

void MarbleMazeMain::RemoveTouch(int id)
{
    m_touches.erase(id);
}

La función PointToTouch traduce la posición actual del puntero para que el origen esté en el centro de la pantalla y, a continuación, escala las coordenadas para que oscilan aproximadamente entre -1.0 y +1.0. Esto facilita el cálculo de la inclinación del laberinto de forma coherente en distintos métodos de entrada.

inline XMFLOAT2 PointToTouch(Windows::Foundation::Point point, Windows::Foundation::Size bounds)
{
    float touchRadius = min(bounds.Width, bounds.Height);
    float dx = (point.X - (bounds.Width / 2.0f)) / touchRadius;
    float dy = ((bounds.Height / 2.0f) - point.Y) / touchRadius;

    return XMFLOAT2(dx, dy);
}

El método MarbleMazeMain::Update actualiza los valores de entrada combinados incrementando el factor de inclinación por un valor de escalado constante. Este valor de escalado se determinó experimentando con varios valores diferentes.

// Account for touch input.
for (TouchMap::const_iterator iter = m_touches.cbegin(); 
    iter != m_touches.cend(); 
    ++iter)
{
    combinedTiltX += iter->second.x * m_touchScaleFactor;
    combinedTiltY += iter->second.y * m_touchScaleFactor;
}

Procesamiento de la entrada del acelerómetro

Para procesar la entrada del acelerómetro, el método MarbleMazeMain::Update llama al método Windows::D evices::Sensors::Accelerometer::GetCurrentReading. Este método devuelve un objeto Windows::D evices::Sensors::AccelerometerReading, que representa una lectura del acelerómetro. Las propiedades Windows::D evices::Sensors::AccelerometerReading::AccelerationX y Windows::D evices::Sensors::AccelerometerReading::AccelerationY contienen la aceleración por fuerza g a lo largo de los ejes X e Y, respectivamente.

En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::Update sondea el acelerómetro y actualiza los valores de entrada combinados. Al inclinar el dispositivo, la gravedad hace que la canica se mueva más rápido.

// Account for sensors.
if (m_accelerometer != nullptr)
{
    Windows::Devices::Sensors::AccelerometerReading^ reading =
        m_accelerometer->GetCurrentReading();

    if (reading != nullptr)
    {
        combinedTiltX += 
            static_cast<float>(reading->AccelerationX) * m_accelerometerScaleFactor;

        combinedTiltY += 
            static_cast<float>(reading->AccelerationY) * m_accelerometerScaleFactor;
    }
}

Dado que no puedes estar seguro de que un acelerómetro está presente en el equipo del usuario, asegúrate siempre de tener un Acelerómetro válido objeto antes de sondear el acelerómetro.

Procesamiento de la entrada del controlador de juego

En el método MarbleMazeMain::Update , usamos m_newReading para procesar la entrada desde el stick analógico izquierdo:

float leftStickX = static_cast<float>(m_newReading.LeftThumbstickX);
float leftStickY = static_cast<float>(m_newReading.LeftThumbstickY);

auto oppositeSquared = leftStickY * leftStickY;
auto adjacentSquared = leftStickX * leftStickX;

if ((oppositeSquared + adjacentSquared) > m_deadzoneSquared)
{
    combinedTiltX += leftStickX * m_controllerScaleFactor;
    combinedTiltY += leftStickY * m_controllerScaleFactor;
}

Comprobamos si la entrada del stick analógico izquierdo está fuera de la zona muerta y, si es así, la agregamos a combinedTiltX y combinedTiltY (multiplicado por un factor de escala) para inclinar la fase.

Importante

Cuando trabajas con un controlador de juego, siempre tienes en cuenta la zona muerta. La zona muerta hace referencia a la varianza entre los controladores para juegos en su sensibilidad al movimiento inicial. En algunos controladores, un pequeño movimiento puede no generar lectura, pero en otros puede generar una lectura medible. Para tener en cuenta esto en tu juego, crea una zona de no movimiento para el movimiento inicial del stick digital. Para obtener más información sobre la zona muerta, ver Lectura de los sticks digitales.

 

Aplicación de la entrada al estado del juego

Los dispositivos notifican valores de entrada de diferentes maneras. Por ejemplo, la entrada del puntero podría estar en coordenadas de pantalla y la entrada del controlador podría estar en un formato completamente diferente. Un desafío con la combinación de la entrada de varios dispositivos en un conjunto de valores de entrada es la normalización o la conversión de valores a un formato común. Marble Maze normaliza los valores al escalarlos al intervalo [-1.0, 1.0]. La función PointToTouch, que se ha descrito anteriormente en esta sección, convierte las coordenadas de pantalla en valores normalizados que oscilan aproximadamente entre -1.0 y +1.0.

Sugerencia

Incluso si la aplicación usa un método de entrada, se recomienda normalizar siempre los valores de entrada. Si lo haces, puedes simplificar la interpretación de la entrada por otros componentes del juego, como la simulación física, y facilita la escritura de juegos que funcionan en diferentes resoluciones de pantalla.

 

Una vez que el método MarbleMazeMain::Update procesa la entrada, crea un vector que representa el efecto de la inclinación del laberinto en la canica. En el ejemplo siguiente se muestra cómo Marble Maze usa la función XMVector3Normalize para crear un vector de gravedad normalizado. La variable maxTilt restringe la cantidad por la que el laberinto inclina y evita que el laberinto se inclina por su lado.

const float maxTilt = 1.0f / 8.0f;

XMVECTOR gravity = XMVectorSet(
    combinedTiltX * maxTilt, 
    combinedTiltY * maxTilt, 
    1.0f, 
    0.0f);

gravity = XMVector3Normalize(gravity);

Para completar la actualización de objetos de escena, Marble Maze pasa el vector de gravedad actualizado a la simulación física, actualiza la simulación física durante el tiempo transcurrido desde el fotograma anterior, y actualiza la posición y orientación de la canica. Si la canica ha caído a través del laberinto, el método MarbleMazeMain::Update coloca la canica en el último punto de control que tocó la canica y restablece el estado de la simulación física.

XMFLOAT3A g;
XMStoreFloat3(&g, gravity);
m_physics.SetGravity(g);

if (m_gameState == GameState::InGameActive)
{
    // Only update physics when gameplay is active.
    m_physics.UpdatePhysicsSimulation(static_cast<float>(m_timer.GetElapsedSeconds()));

    // ...Code omitted for simplicity...

}

// ...Code omitted for simplicity...

// Check whether the marble fell off of the maze. 
const float fadeOutDepth = 0.0f;
const float resetDepth = 80.0f;
if (marblePosition.z >= fadeOutDepth)
{
    m_targetLightStrength = 0.0f;
}
if (marblePosition.z >= resetDepth)
{
    // Reset marble.
    memcpy(&marblePosition, &m_checkpoints[m_currentCheckpoint], sizeof(XMFLOAT3));
    oldMarblePosition = marblePosition;
    m_physics.SetPosition((const XMFLOAT3&)marblePosition);
    m_physics.SetVelocity(XMFLOAT3(0, 0, 0));
    m_lightStrength = 0.0f;
    m_targetLightStrength = 1.0f;

    m_resetCamera = true;
    m_resetMarbleRotation = true;
    m_audio.PlaySoundEffect(FallingEvent);
}

En esta sección no se describe cómo funciona la simulación física. Para obtener más información sobre esto, ver Physics.h y Physics.cpp en las fuentes de Marble Maze.

Pasos siguientes

Lee Agregar audio al ejemplo de Marble Mazepara obtener información sobre algunas de las prácticas clave que debe tener en cuenta al trabajar con audio. En el documento se describe cómo Marble Maze usa Microsoft Media Foundation y XAudio2 para cargar, mezclar y reproducir recursos de audio.