Ajout d’entrée et d’interactivité à l’exemple Marble Maze

Les jeux de plateforme Windows universelle (UWP) s’exécutent sur un large éventail d’appareils, tels que les ordinateurs de bureau, les ordinateurs portables et les tablettes. Un appareil peut avoir une pléthore de mécanismes d’entrée et de contrôle. Ce document décrit les pratiques clés à garder à l’esprit lorsque vous travaillez avec des appareils d’entrée et montre comment Marble Maze applique ces pratiques.

Remarque

L’exemple de code correspondant à ce document se trouve dans l’exemple de jeu DirectX Marble Maze .

  Voici quelques-uns des points clés que ce document décrit lorsque vous travaillez avec une entrée dans votre jeu :

  • Si possible, prenez en charge plusieurs appareils d’entrée pour permettre à votre jeu de prendre en charge un plus large éventail de préférences et de capacités parmi vos clients. Bien que l’utilisation du contrôleur de jeu et du capteur soit facultative, nous vous recommandons vivement d’améliorer l’expérience du joueur. Nous avons conçu les API de contrôleur de jeu et de capteur pour vous aider à intégrer plus facilement ces appareils d’entrée.

  • Pour initialiser l’interaction tactile, vous devez vous inscrire aux événements de fenêtre tels que lorsque le pointeur est activé, libéré et déplacé. Pour initialiser l’accéléromètre, créez un objet Windows::Devices::Sensors::Accelerometer lorsque vous initialisez l’application. Un contrôleur de jeu ne nécessite pas d’initialisation.

  • Pour les jeux à joueur unique, déterminez s’il faut combiner les entrées de tous les contrôleurs possibles. De cette façon, vous n’avez pas besoin de suivre l’entrée provenant de quel contrôleur. Ou, effectuez simplement le suivi de l’entrée uniquement à partir du contrôleur le plus récemment ajouté, comme nous le faisons dans cet exemple.

  • Traiter les événements Windows avant de traiter les appareils d’entrée.

  • Le contrôleur de jeu et l’accéléromètre prennent en charge l’interrogation. Autrement dit, vous pouvez interroger les données quand vous en avez besoin. Pour l’interaction tactile, enregistrez les événements tactiles dans les structures de données disponibles pour votre code de traitement d’entrée.

  • Déterminez s’il faut normaliser les valeurs d’entrée dans un format commun. Cela peut simplifier la façon dont l’entrée est interprétée par d’autres composants de votre jeu, tels que la simulation physique, et peut faciliter l’écriture de jeux qui fonctionnent sur différentes résolutions d’écran.

Périphériques d’entrée pris en charge par Marble Maze

Marble Maze prend en charge le contrôleur de jeu, la souris et l’interaction tactile pour sélectionner des éléments de menu, ainsi que le contrôleur de jeu, la souris, l’interaction tactile et l’accéléromètre pour contrôler le jeu. Marble Maze utilise les API Windows::Gaming::Input pour interroger le contrôleur pour obtenir des entrées. L’interaction tactile permet aux applications de suivre et de répondre à l’entrée du bout des doigts. Un accéléromètre est un capteur qui mesure la force appliquée le long des axes X, Y et Z. À l’aide de Windows Runtime, vous pouvez interroger l’état actuel de l’appareil accéléromètre, ainsi que recevoir des événements tactiles via le mécanisme de gestion des événements Windows Runtime.

Remarque

Ce document utilise le terme "tactile" pour faire référence à la fois à l'entrée tactile et à l'entrée de la souris, et "pointeur" pour désigner tout appareil utilisant des événements de pointeur. Étant donné que l’interaction tactile et la souris utilisent des événements de pointeur standard, vous pouvez utiliser l’un ou l’autre appareil pour sélectionner des éléments de menu et contrôler le jeu de jeu.

 

Remarque

Le manifeste du package définit Paysage comme la seule rotation prise en charge pour le jeu afin d’empêcher l’orientation de changer lorsque vous faites pivoter l’appareil pour rouler la bille. Pour afficher le manifeste du package, ouvrez Package.appxmanifest dans l’Explorateur de solutions dans Visual Studio.

 

Initialisation des appareils d’entrée

Le contrôleur de jeu ne nécessite pas d’initialisation. Pour initialiser le tactile, vous devez enregistrer les événements de fenêtrage tels que lorsque le pointeur est activé (par exemple, l'utilisateur appuie sur le bouton de la souris ou touche l’écran), libéré et déplacé. Pour initialiser l’accéléromètre, vous devez créer un objet Windows::Devices::Sensors::Accelerometer lors de l'initialisation de l'application.

L’exemple suivant montre comment la méthode App::SetWindow s'enregistre pour les événements de pointeur Windows::UI::Core::CoreWindow::PointerPressed, Windows::UI::Core::CoreWindow::PointerReleased, et Windows::UI::Core::CoreWindow::PointerMoved. Ces événements sont enregistrés pendant l’initialisation de l’application et avant la boucle de jeu.

Ces événements sont gérés dans un thread distinct qui appelle les gestionnaires d’événements.

Pour plus d’informations sur l’initialisation de l’application, consultez la structure de l’application 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 classe MarbleMazeMain crée également un objet std ::map pour contenir les événements tactiles. La clé de cet objet map est une valeur qui identifie de manière unique le pointeur d’entrée. Chaque touche correspond à la distance entre chaque point tactile et le centre de l’écran. Marble Maze utilise ultérieurement ces valeurs pour calculer la quantité par laquelle le labyrinthe est incliné.

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

La classe MarbleMazeMain contient également un objet Accelerometer.

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

L’objet Accelerometer est initialisé dans le constructeur MarbleMazeMain, comme illustré dans l’exemple suivant. La méthode Windows::Devices::Sensors::Accelerometer::GetDefault retourne une instance de l’accéléromètre par défaut. S’il n’existe aucun accéléromètre par défaut, Accelerometer ::GetDefault retourne nullptr.

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

Vous pouvez utiliser la souris, l’interaction tactile ou un contrôleur de jeu pour parcourir les menus, comme suit :

  • Utilisez le panneau directionnel pour modifier l’élément de menu actif.
  • Utilisez l’interaction tactile, le bouton A ou le bouton Menu pour sélectionner un élément de menu ou fermer le menu actif, tel que le tableau à score élevé.
  • Utilisez le bouton Menu pour suspendre ou reprendre le jeu.
  • Cliquez sur un élément de menu avec la souris pour choisir cette action.

Suivi de l’entrée du contrôleur de jeu

Pour suivre les manettes actuellement connectées à l’appareil, MarbleMazeMain définit une variable membre, m_myGamepads, qui est une collection d’objets Windows::Gaming::Input::Gamepad. Ceci est initialisé dans le constructeur comme suit :

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

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

En outre, le constructeur MarbleMazeMain inscrit des événements lorsque les boîtiers de commande sont ajoutés ou supprimés :

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;
    }
});

Lorsqu’un boîtier de commande est ajouté, il est ajouté à m_myGamepads ; lorsqu’un boîtier de commande est supprimé, nous vérifions si le boîtier de commande est en m_myGamepads, et si c’est le cas, nous le supprimons. Dans les deux cas, nous définissons m_currentGamepadNeedsRefresh sur true, ce qui indique que nous devons réaffecter m_gamepad.

Enfin, nous affectons une manette de jeu à m_gamepad et définissons m_currentGamepadNeedsRefresh sur false:

m_gamepad = GetLastGamepad();
m_currentGamepadNeedsRefresh = false;

Dans la méthode Update , nous vérifions si m_gamepad doit être réaffecté :

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

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

    m_currentGamepadNeedsRefresh = false;
}

Si m_gamepad doit être réaffecté, nous lui affectons le boîtier de commande le plus récemment ajouté, à l’aide de GetLastGamepad, qui est défini comme suit :

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

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

    return gamepad;
}

Cette méthode retourne simplement la dernière manette dans m_myGamepads.

Vous pouvez connecter jusqu’à quatre contrôleurs de jeu à un appareil Windows 10. Pour éviter d’avoir à déterminer quel contrôleur est le contrôleur actif, nous suivons simplement le boîtier de commande le plus récemment ajouté. Si votre jeu prend en charge plusieurs joueurs, vous devez suivre les entrées de chaque joueur séparément.

La méthode MarbleMazeMain::Update interroge la manette pour les entrées :

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

Nous suivons la lecture de l’entrée que nous avons obtenue dans le dernier cadre avec m_oldReading, et la dernière lecture d’entrée avec m_newReading, que nous obtenons en appelant Gamepad ::GetCurrentReading. Cela renvoie un objet GamepadReading , qui contient des informations sur l’état actuel du boîtier de commande.

Pour vérifier si un bouton vient d’être appuyé ou libéré, nous définissons MarbleMazeMain ::ButtonJustPressed et MarbleMazeMain ::ButtonJustReleased, qui comparent les lectures de boutons à partir de ce cadre et du dernier cadre. De cette façon, nous pouvons effectuer une action uniquement au moment où un bouton est initialement enfoncé ou relâché, et non quand il est conservé :

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;
}

Les lectures gamepadButtons sont comparées à l’aide d’opérations au niveau du bit : nous vérifions si un bouton est appuyé à l’aide du bit et (&). Nous déterminons si un bouton vient d’être appuyé ou libéré en comparant l’ancienne lecture et la nouvelle lecture.

À l’aide des méthodes ci-dessus, nous vérifions si certains boutons ont été enfoncés et effectuez les actions correspondantes qui doivent se produire. Par exemple, lorsque le bouton Menu (GamepadButtons ::Menu) est enfoncé, l’état du jeu passe d’actif à suspendu ou suspendu à actif.

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);
    }
}

Nous vérifions également si le joueur appuie sur le bouton Affichage, auquel cas nous redémarrons le jeu ou décochons la table de score élevé :

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 le menu principal est actif, l’élément de menu actif change lorsque le pavé directionnel est enfoncé vers le haut ou le bas. Si l’utilisateur choisit la sélection actuelle, l’élément d’interface utilisateur approprié est marqué comme étant choisi.

// 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;
}

Suivi de l’entrée tactile et de la souris

Pour l’entrée tactile et souris, un élément de menu est choisi lorsque l’utilisateur touche ou clique dessus. L’exemple suivant montre comment la méthode MarbleMazeMain ::Update traite l’entrée du pointeur pour sélectionner des éléments de menu. La variable membre m_pointQueue suit les emplacements où l’utilisateur a touché ou cliqué sur l’écran. La façon dont Marble Maze collecte les entrées de pointeur est décrite plus en détail plus loin dans ce document dans la section Traitement de l’entrée du pointeur.

// 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();
}

La méthode UserInterface ::HitTest détermine si le point fourni se trouve dans les limites d’un élément d’interface utilisateur. Tous les éléments d’interface utilisateur qui réussissent ce test sont marqués comme étant touchés. Cette méthode utilise la fonction d’assistance PointInRect pour déterminer si le point fourni se trouve dans les limites de chaque élément d’interface utilisateur.

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));
        }
    }
}

Mise à jour de l’état du jeu

Une fois que la méthode MarbleMazeMain::Update a traité le contrôle du contrôleur et les entrées tactiles, elle met à jour l'état du jeu si un bouton a été pressé.

// 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);
}

Contrôle du jeu

La boucle de jeu et la méthode MarbleMazeMain ::Update fonctionnent ensemble pour mettre à jour l’état des objets de jeu. Si votre jeu accepte l’entrée de plusieurs appareils, vous pouvez accumuler l’entrée de tous les appareils dans un ensemble de variables afin que vous puissiez écrire du code plus facile à gérer. La méthode MarbleMazeMain::Update définit un ensemble de variables qui accumulent le mouvement à partir de tous les appareils.

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

Le mécanisme d’entrée peut varier d’un appareil d’entrée à un autre. Par exemple, l’entrée de pointeur est gérée à l’aide du modèle de gestion des événements Windows Runtime. À l’inverse, vous interrogez les données d’entrée du contrôleur de jeu quand vous en avez besoin. Nous vous recommandons de toujours suivre le mécanisme d’entrée prescrit pour un appareil donné. Cette section décrit comment Marble Maze lit les entrées de chaque appareil, comment elle met à jour les valeurs d’entrée combinées et comment elle utilise les valeurs d’entrée combinées pour mettre à jour l’état du jeu.

Traitement de l’entrée du pointeur

Lorsque vous utilisez une entrée de pointeur, appelez la méthode Windows ::UI ::Core ::CoreDispatcher ::P rocessEvents pour traiter les événements de fenêtre. Appelez cette méthode dans votre boucle de jeu avant de mettre à jour ou de restituer la scène. Marble Maze appelle ceci dans la méthode 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 fenêtre est visible, nous transmettons CoreProcessEventsOption ::P rocessAllIfPresent à ProcessEvents pour traiter tous les événements mis en file d’attente et retourner immédiatement ; sinon, nous transmettons CoreProcessEventsOption ::P rocessOneAndAllPending pour traiter tous les événements mis en file d’attente et attendre le nouvel événement suivant. Une fois les événements traités, Marble Maze affiche et présente le cadre suivant.

Windows Runtime appelle le gestionnaire inscrit pour chaque événement qui s’est produit. La méthode App ::SetWindow s’inscrit pour les événements et transfère les informations de pointeur vers la classe 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 classe MarbleMazeMain réagit aux événements de pointeur en mettant à jour l’objet map qui contient les événements tactiles. La méthode MarbleMazeMain ::AddTouch est appelée lorsque le pointeur est appuyé pour la première fois sur l’écran, par exemple lorsque l’utilisateur touche initialement l’écran sur un appareil tactile. La méthode MarbleMazeMain ::UpdateTouch est appelée lorsque la position du pointeur se déplace. La méthode MarbleMazeMain ::RemoveTouch est appelée lorsque le pointeur est libéré, par exemple lorsque l’utilisateur cesse de toucher l’écran.

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 fonction PointToTouch traduit la position actuelle du pointeur afin que l’origine se trouve au centre de l’écran, puis met à l’échelle les coordonnées afin qu’elles s’étendent environ entre -1.0 et +1.0. Cela facilite le calcul de l’inclinaison du labyrinthe de manière cohérente entre différentes méthodes d’entrée.

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);
}

La méthode MarbleMazeMain ::Update met à jour les valeurs d’entrée combinées en incrémentant le facteur d’inclinaison par une valeur de mise à l’échelle constante. Cette valeur de mise à l’échelle a été déterminée par l’expérimentation de plusieurs valeurs différentes.

// 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;
}

Traitement de l’entrée d’accéléromètre

Pour traiter l’entrée d’accéléromètre, la méthode MarbleMazeMain::Update appelle la méthode Windows::Devices::Sensors::Accelerometer::GetCurrentReading. Cette méthode renvoie un objet Windows::Devices::Sensors::AccelerometerReading, qui représente une lecture d’accéléromètre. Les propriétés Windows::Devices::Sensors::AccelerometerReading::AccelerationX et Windows::Devices::Sensors::AccelerometerReading::AccelerationY contiennent l'accélération de la force g le long des axes X et Y, respectivement.

L’exemple suivant montre comment la méthode MarbleMazeMain ::Update interroge l’accéléromètre et met à jour les valeurs d’entrée combinées. Lorsque vous inclinez l’appareil, la gravité provoque le déplacement plus rapide de la bille.

// 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;
    }
}

Étant donné que vous ne pouvez pas être sûr qu’un accéléromètre est présent sur l’ordinateur de l’utilisateur, assurez-vous toujours que vous disposez d’un objet Accelerometer valide avant d’interroger l’accéléromètre.

Traitement de l’entrée du contrôleur de jeu

Dans la méthode MarbleMazeMain ::Update , nous utilisons m_newReading pour traiter l’entrée à partir du stick analogique gauche :

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;
}

Nous vérifions si l’entrée du stick analogique gauche est en dehors de la zone morte, et si c’est le cas, nous l’ajoutons à combinedTiltX et combinedTiltY (multipliée par un facteur d’échelle) pour incliner la scène.

Important

Lorsque vous travaillez avec un contrôleur de jeu, comptez toujours pour la zone morte. La zone morte fait référence à la variance entre les boîtiers de commande dans leur sensibilité au mouvement initial. Dans certains contrôleurs, un petit mouvement peut générer aucune lecture, mais dans d’autres, il peut générer une lecture mesurable. Pour tenir compte de cela dans votre jeu, créez une zone de non-mouvement pour le mouvement initial du stick. Pour plus d’informations sur la zone morte, consultez Lecture des touches.

 

Application d’une entrée à l’état du jeu

Les appareils signalent des valeurs d’entrée de différentes façons. Par exemple, l’entrée de pointeur peut se trouver dans les coordonnées de l’écran, et l’entrée du contrôleur peut se trouver dans un format complètement différent. L’un des défis liés à la combinaison des entrées de plusieurs appareils en un ensemble de valeurs d’entrée est la normalisation ou la conversion de valeurs dans un format commun. Marble Maze normalise les valeurs en les ajustant à la plage [-1.0, 1.0]. La fonction PointToTouch , décrite précédemment dans cette section, convertit les coordonnées de l’écran en valeurs normalisées comprises entre -1.0 et +1.0.

Conseil / Astuce

Même si votre application utilise une méthode d’entrée, nous vous recommandons de normaliser toujours les valeurs d’entrée. Cela peut simplifier la façon dont l’entrée est interprétée par d’autres composants de votre jeu, tels que la simulation physique, et facilite l’écriture de jeux qui fonctionnent sur différentes résolutions d’écran.

 

Après que la méthode MarbleMazeMain::Update a traité les entrées, elle crée un vecteur qui représente l'effet de l'inclinaison du labyrinthe sur la bille. L’exemple suivant montre comment Marble Maze utilise la fonction XMVector3Normalize pour créer un vecteur de gravité normalisé. La variable maxTilt limite l'inclinaison du labyrinthe et empêche le labyrinthe de s'incliner sur le côté.

const float maxTilt = 1.0f / 8.0f;

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

gravity = XMVector3Normalize(gravity);

Pour terminer la mise à jour des objets de scène, Marble Maze transmet le vecteur de gravité mis à jour à la simulation physique, met à jour la simulation physique pour le temps écoulé depuis le cadre précédent, et met à jour la position et l’orientation du marbre. Si la bille est tombée dans le labyrinthe, la méthode MarbleMazeMain::Update place la bille au dernier point de contrôle que la bille a touché et réinitialise l’état de la simulation physique.

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);
}

Cette section ne décrit pas le fonctionnement de la simulation physique. Pour plus d’informations, consultez Physics.h et Physics.cpp dans les sources Marble Maze.

Étapes suivantes

Lisez l’exemple d’ajout d’audio à l’exemple Marble Maze pour plus d’informations sur certaines des pratiques clés à garder à l’esprit lorsque vous travaillez avec l’audio. Le document explique comment Marble Maze utilise Microsoft Media Foundation et XAudio2 pour charger, mélanger et lire des ressources audio.