Ajouter des données d'entrée et d'interactivité à l'échantillon Marble Maze
Les jeux de plateforme Windows universelle (UWP) s’exécutent sur un large éventail d’appareils, comme les ordinateurs de bureau, les ordinateurs portables et les tablettes. Un appareil peut avoir de nombreux 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 périphériques d’entrée et montre comment Marble Maze applique ces pratiques.
Remarque
L'exemple de code qui correspond à ce document se trouve dans l'échantillon de jeu Marble Maze DirectX.
Voici quelques-uns des points clés abordés dans ce document, pour les moments où vous travaillez avec les entrées dans votre jeu :
Si possible, prenez en charge plusieurs périphériques 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 le recommandons vivement, afin 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 périphériques d’entrée.
Pour initialiser l’interaction tactile, vous devez vous inscrire aux évènements de fenêtre, comme lorsque le pointeur est activé, relâché et déplacé. Pour initialiser l’accéléromètre, créez un objet Windows::Devices::Sensors::Accelerometer lorsque vous initialisez l’application. Les contrôleurs de jeu ne nécessitent 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 savoir de quel contrôleur provient quelle entrée. Ou, effectuez simplement le suivi d’entrée uniquement à partir du contrôleur le plus récemment ajouté, comme nous le faisons dans cet exemple.
Traitez les évènements Windows avant de traiter les périphériques 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, comme la simulation physique, et peut faciliter l’écriture de jeux qui fonctionnent avec 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 sur les entrées. L’interaction tactile permet aux applications de suivre et de répondre aux entrées 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 du périphérique 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 « tactile ’ pour faire référence à la fois à l’entrée de l’interaction tactile et à l’entrée de la souris, et « pointeur » pour faire référence à tous les périphériques qui utilisent des évènements de pointeur. L’interaction tactile et la souris utilisant 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 gameplay.
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 faire rouler la bille. Pour afficher le manifeste du package, ouvrez Package.appxmanifest dans l’Explorateur de solutions de Visual Studio.
Initialiser les périphériques d'entrée
Le contrôleur de jeu ne nécessite pas d’initialisation. Pour initialiser l’interaction tactile, vous devez vous inscrire aux évènements de fenêtrage, comme lorsque le pointeur est activé (par exemple, le lecteur appuie sur le bouton de la souris ou touche l’écran), relâché et déplacé. Pour initialiser l’accéléromètre, vous devez créer un objet Windows::Devices::Sensors::Accelerometer lorsque vous initialisez l’application.
L’exemple suivant montre comment la méthode App::SetWindow s’inscrit aux é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 d’interaction tactile. 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 tous les points tactiles et le centre de l’écran. Marble Maze utilise ultérieurement ces valeurs pour calculer le niveau d’inclinaison du labyrinthe.
typedef std::map<int, XMFLOAT2> TouchMap;
TouchMap m_touches;
La classeMarbleMazeMain 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 renvoie 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();
Naviguer dans les menus
Vous pouvez utiliser la souris, l’interaction tactile ou un contrôleur de jeu pour parcourir les menus, comme suit :
- Utilisez le pavé 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, comme le tableau des meilleurs scores.
- Utilisez le bouton Menu pour suspendre ou reprendre le jeu.
- Cliquez sur un élément de menu avec la souris pour choisir cette action.
Suivre les entrées du contrôleur de jeu
Pour suivre les boîtiers de commande actuellement connectés à l’appareil, MarbleMazeMaindé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 des 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 se trouve dans 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 un boîtier de commande à 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 le dernier boîtier de commande 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 le boîtier de commande sur les entrées :
if (m_gamepad != nullptr)
{
m_oldReading = m_newReading;
m_newReading = m_gamepad->GetCurrentReading();
}
Nous suivons la lecture d’entrée que nous avons obtenue dans la dernière trame 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 actionné ou relâché, nous définissons MarbleMazeMain::ButtonJustPressed et MarbleMazeMain::ButtonJustReleased, qui comparent les lectures des boutons de cette trame et la dernière trame. De cette façon, nous pouvons effectuer une action uniquement au moment où un bouton est initialement actionné ou relâché, et non quand il est maintenu :
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 actionné au niveau du bit et (et). Nous déterminons si un bouton vient d’être actionné ou relâché en comparant l’ancienne lecture et la nouvelle.
À l’aide des méthodes ci-dessus, nous vérifions si certains boutons ont été actionnés et si les actions correspondantes qui doivent se produire ont été effectuées. Par exemple, lorsque le bouton Menu (GamepadButtons::Menu) est actionné, l’état du jeu passe d’actif à suspendu ou de 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 effaçons le tableau des meilleurs scores :
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 actionné 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;
}
Suivre les entrées de l’interaction tactile et de la souris
Pour l’entrée de l’interaction tactile et de la souris, un élément de menu est choisi lorsque l’utilisateur le touche ou clique dessus. L’exemple suivant montre comment la méthode MarbleMazeMain::Update traite l’entrée du pointeur pour sélectionner les éléments de menu. La variable membre m_pointQueue suit les emplacements que l’utilisateur a touchés ou sur lesquels il a cliqué. La façon dont Marble Maze collecte l’entrée du pointeur est décrite en détail plus loin dans ce document, dans la section Traiter les entrées de 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 passent ce test sont marqués comme étant touchés. Cette méthode utilise la fonction d’application auxiliaire 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));
}
}
}
Mettre à jour l’état du jeu
Une fois que la méthode MarbleMazeMain::Update a traité les entrées du contrôleur et d’interaction tactile, elle met à jour l’état du jeu si un bouton a été actionné.
// 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ôler le gameplay
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 des entrées de plusieurs appareils, vous pouvez accumuler l’entrée de tous les appareils dans un ensemble de variables afin de pouvoir é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 périphériques.
float combinedTiltX = 0.0f;
float combinedTiltY = 0.0f;
Le mécanisme d’entrée peut varier d’un périphérique 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 périphérique donné. Cette section décrit comment Marble Maze lit les entrées de chaque périphérique, 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.
Traiter les entrées de pointeur
Lorsque vous travaillez avec des entrées de pointeur, appelez la méthode Windows::UI::Core::CoreDispatcher::ProcessEvents pour traiter les évènements de fenêtre. Appelez cette méthode dans votre boucle de jeu avant la mise à jour ou le rendu de 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::ProcessAllIfPresent à ProcessEvents pour traiter tous les évènements mis en file d’attente et retourner immédiatement. Dans le cas contraire, nous transmettons CoreProcessEventsOption::ProcessOneAndAllPending 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 la trame suivante.
Windows Runtime appelle le gestionnaire inscrit pour chaque évènement qui s’est produit. La méthode App::SetWindow s’inscrit aux é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 d’interaction tactile. La méthode MarbleMazeMain::AddTouch est appelée lorsque le pointeur est actionné pour la première fois, 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 change. La méthode MarbleMazeMain::RemoveTouch est appelée lorsque le pointeur est relâché, 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 dans une plage comprise entre -1,0 et +1,0 environ. Cela facilite le calcul de l’inclinaison du labyrinthe de manière cohérente entre les 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 une expérimentation avec 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;
}
Traiter les entrées d’accéléromètre
Pour traiter les entrées 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 la force g d’accélération sur les 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 un 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 soit 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.
Traiter les entrées de contrôleur de jeu
Dans la méthode MarbleMazeMain::Update, nous utilisons m_newReading pour traiter l’entrée depuis le 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é par un facteur d’échelle) pour incliner la scène.
Important
Lorsque vous travaillez avec un contrôleur de jeu, tenez toujours compte de la zone morte. La zone morte fait référence à la variance entre les boîtiers de commande, dans leur sensibilité au mouvement initial. Avec certains contrôleurs, un petit mouvement pourra ne générer aucune lecture, mais avec d’autres, il pourra 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 joystick. Pour plus d’informations sur la zone morte, consultez Lire les joysticks.
Appliquer une entrée à l’état du jeu
Les appareils signalent les valeurs d’entrée de différentes façons. Par exemple, l’entrée de pointeur peut se composer de coordonnées sur l’écran, et l’entrée du contrôleur peut être 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 mettant à l’échelle dans 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
Même si votre application n’utilise qu’une méthode d’entrée, nous vous recommandons de toujours normaliser 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 avec différentes résolutions d’écran.
Une fois 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 définit l’inclinaison du labyrinthe et empêche son retournement.
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 la trame précédente et met à jour la position et l’orientation de la bille. Si la bille est tombée dans le labyrinthe, la méthode MarbleMazeMain::Update la replace au dernier point de contrôle qu’elle a atteint 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 Ajout d’audio à l’échantillon Marble Maze pour plus d’informations sur certaines des pratiques clés à garder à l’esprit lorsque vous travaillez avec de l’audio. Le document explique comment Marble Maze utilise Microsoft Media Foundation et XAudio2 pour charger, mélanger et lire des ressources audio.
Rubriques connexes
- Ajouter de l’audio à l’échantillon Marble Maze
- Ajouter un contenu visuel à l’échantillon Marble Maze
- Développer Marble Maze, un jeu UWP en C++ et DirectX