Ajouter des contrôles
Remarque
Cette rubrique fait partie de la série de tutoriels Créer un jeu simple de plateforme Windows universelle simple (UWP) avec DirectX. La rubrique accessible via ce lien définit le contexte de la série.
[ Mise à jour pour les applications UWP sur Windows 10. Pour les articles sur Windows 8.x, consultez l’archive ]
Un bon jeu UWP (Plateforme Windows universelle) prend en charge une grande variété d’interfaces. Un joueur potentiel peut avoir Windows 10 sur une tablette sans aucun bouton physique, un ordinateur multimédia équipé d’une manette Xbox 360, ou l’ordinateur de jeu personnalisé avec une souris et un clavier très performants pour les jeux. Dans notre jeu, les contrôles sont implémentés dans la classe MoveLookController. Cette classe regroupe les trois types d’entrée (souris et clavier, tactile et boîtier de commande) dans un seul contrôleur. Le résultat final est un jeu de tir à la première personne (FPS, First-Person Shooter) qui utilise des contrôles de mouvement/vue standard du genre fonctionnant avec plusieurs appareils.
Remarque
Pour plus d’informations sur les contrôles, consultez Contrôles de mouvement/vue pour les jeux et Contrôles tactiles pour les jeux.
Objectif
À ce stade, nous avons un jeu qui s’affiche, mais nous ne pouvons pas déplacer notre joueur ou tirer sur des cibles. Nous allons examiner comment notre jeu implémente des contrôles de mouvement/vue FPS pour les types d’entrée suivants dans notre jeu UWP DirectX.
- Souris et clavier
- Toucher
- Boîtier de commande
Remarque
Si vous n’avez pas téléchargé le code du jeu le plus récent pour cet exemple, accédez à Exemple de jeu Direct3D. Cet exemple fait partie d’une grande collection d’exemples de fonctionnalités UWP. Pour obtenir des instructions sur le téléchargement de l’exemple, consultez Exemples d’applications pour le développement Windows.
Comportements des contrôles courants
Les contrôles tactiles et les contrôles de souris/clavier ont une implémentation de base très similaire. Dans une application UWP, un pointeur est simplement un point à l’écran. Vous pouvez le déplacer en faisant glisser la souris ou votre doigt sur l’écran tactile. Vous pouvez donc vous inscrire à un seul ensemble d’événements, et ne pas vous soucier de savoir si le joueur utilise une souris ou un écran tactile pour se déplacer et appuyer sur le pointeur.
Quand la classe MoveLookController de l’exemple de jeu est initialisée, elle s’inscrit à quatre événements spécifique au pointeur et à un événement spécifique à la souris :
Événement | Description |
---|---|
CoreWindow::PointerPressed | Le bouton gauche ou droit de la souris a été enfoncé (et maintenu), ou la surface tactile a été touchée. |
CoreWindow::PointerMoved | La souris a été déplacée ou une action de glisser a été effectuée sur la surface tactile. |
CoreWindow::PointerReleased | Le bouton gauche de la souris a été relâché ou l’objet au contact de la surface tactile a été relevé. |
CoreWindow::PointerExited | Le pointeur a été déplacé en dehors de la fenêtre principale. |
Windows::Devices::Input::MouseMoved | La souris a été déplacée sur une certaine distance. Notez que nous sommes intéressés seulement par les valeurs relatives des mouvements de la souris, et non pas par la position X-Y actuelle. |
Ces gestionnaires d’événements sont définis pour commencer à écouter les entrées utilisateur dès que MoveLookController est initialisé dans la fenêtre de l’application.
void MoveLookController::InitWindow(_In_ CoreWindow const& window)
{
ResetState();
window.PointerPressed({ this, &MoveLookController::OnPointerPressed });
window.PointerMoved({ this, &MoveLookController::OnPointerMoved });
window.PointerReleased({ this, &MoveLookController::OnPointerReleased });
window.PointerExited({ this, &MoveLookController::OnPointerExited });
...
// There is a separate handler for mouse-only relative mouse movement events.
MouseDevice::GetForCurrentView().MouseMoved({ this, &MoveLookController::OnMouseMoved });
...
}
Le code complet pour InitWindow est visible sur GitHub.
Pour déterminer quand le jeu doit être à l’écoute de certaines entrées, la classe MoveLookController a trois états spécifiques au contrôleur, quel que soit le type de contrôleur :
State | Description |
---|---|
Aucun | C’est l’état initialisé du contrôleur. Toutes les entrées sont ignorées, car le jeu n’anticipe aucune entrée du contrôleur. |
WaitForInput | Le contrôleur attend que le joueur accuse réception d’un message du jeu via un clic gauche de la souris, un événement tactile, ou le bouton de menu d’un boîtier de commande. |
Activé | Le contrôleur est en mode de jeu actif. |
État WaitForInput et mise en pause du jeu
Le jeu passe à l’état WaitForInput quand le jeu a été mis en pause. Ceci se produit quand le joueur déplace le pointeur en dehors de la fenêtre principale du jeu ou appuie sur le bouton de mise en pause (la touche P ou le bouton Démarrer du boîtier de commande). MoveLookController inscrit l’appui et informe la boucle du jeu quand il appelle la méthode IsPauseRequested. À ce stade, si IsPauseRequested retourne la valeur true, la boucle du jeu appelle alors WaitForPress sur MoveLookController pour placer le contrôleur dans l’état WaitForInput.
Une fois dans l’état WaitForInput, le jeu cesse de traiter presque tous les événements d’entrée du jeu, jusqu’à ce qu’il revienne à l’état Active (Actif). L’exception est le bouton de mise en pause qui, quand l’utilisateur appuie sur celui-ci, provoque le retour du jeu à l’état actif. En dehors du bouton de mise en pause, pour que le jeu revienne à l’état Active, le joueur doit sélectionner un élément de menu.
L’état Active (Actif)
Pendant que l’état est Active, l’instance de MoveLookController traite les événements provenant de tous les appareils d’entrée activés et interprète les intentions du joueur. Il met donc à jour la vélocité et la direction de la vue du joueur, et il partage les données mises à jour avec le jeu après l’appel de Updatedepuis la boucle du jeu.
Toutes les entrées du pointeur sont suivies dans l’état Active, avec différents ID de pointeur correspondant aux différentes actions du pointeur. Quand un événement PointerPressed est reçu, MoveLookController obtient la valeur de l’ID du pointeur créée par la fenêtre. L’ID de pointeur représente un type d’entrée spécifique. Par exemple, sur un appareil à plusieurs touches, il peut y avoir plusieurs entrées différentes actives en même temps. Les ID sont utilisés pour suivre l’entrée utilisée par le joueur. Si un événement se trouve dans le rectangle de mouvement de l’écran tactile, un ID de pointeur est affecté pour suivre tous les événements de pointeur dans le rectangle de mouvement. Les autres événements de pointeur dans le rectangle de déclenchement sont suivis séparément, avec un ID de pointeur distinct.
Remarque
Les entrées de la souris et de la manette droite d’un boîtier de commande ont également des ID qui sont gérés séparément.
Une fois que les événements de pointeur ont été mappés à une action spécifique du jeu, il est temps de mettre à jour les données que l’objet MoveLookController partage avec la boucle principale du jeu.
Quand elle est appelée, la méthode Update de l’exemple de jeu traite l’entrée et met à jour les variables de vélocité et de direction de la vue (m_velocity et m_lookdirection), que la boucle du jeu récupère ensuite en appelant les méthodes publiques Velocity et LookDirection..
Remarque
Vous trouverez plus d’informations sur la méthode Update plus loin dans cette page.
La boucle du jeu peut tester si le joueur tire en appelant la méthode IsFiring sur l’instance MoveLookController. MoveLookController vérifie si le lecteur a appuyé sur le bouton pour tirer sur un des trois types d’entrée.
bool MoveLookController::IsFiring()
{
if (m_state == MoveLookControllerState::Active)
{
if (m_autoFire)
{
return (m_fireInUse || (m_mouseInUse && m_mouseLeftInUse) || PollingFireInUse());
}
else
{
if (m_firePressed)
{
m_firePressed = false;
return true;
}
}
}
return false;
}
Examinons à présent l’implémentation de chacun des trois types de contrôle un peu plus en détail.
Ajout de contrôles de souris associés
Si un mouvement de la souris est détecté, nous voulons utiliser ce mouvement pour déterminer le nouveau tangage et le nouveau lacet de la caméra. Nous faisons cela en implémentant des contrôles de souris associés, où nous gérons la distance relative du mouvement de la souris (le delta entre le début du mouvement et l’arrêt), au lieu d’enregistrer les coordonnées absolues des pixels x-y du mouvement.
Pour cela, nous obtenons les modifications apportées aux coordonnées X (mouvement horizontal) et Y (mouvement vertical) en examinant les champs MouseDelta::X et MouseDelta::Y sur l’objetWindows::Device::Input::MouseEventArgs::MouseDelta retourné par l’événement MouseMoved..
void MoveLookController::OnMouseMoved(
_In_ MouseDevice const& /* mouseDevice */,
_In_ MouseEventArgs const& args
)
{
// Handle Mouse Input via dedicated relative movement handler.
switch (m_state)
{
case MoveLookControllerState::Active:
XMFLOAT2 mouseDelta;
mouseDelta.x = static_cast<float>(args.MouseDelta().X);
mouseDelta.y = static_cast<float>(args.MouseDelta().Y);
XMFLOAT2 rotationDelta;
// Scale for control sensitivity.
rotationDelta.x = mouseDelta.x * MoveLookConstants::RotationGain;
rotationDelta.y = mouseDelta.y * MoveLookConstants::RotationGain;
// Update our orientation based on the command.
m_pitch -= rotationDelta.y;
m_yaw += rotationDelta.x;
// Limit pitch to straight up or straight down.
float limit = XM_PI / 2.0f - 0.01f;
m_pitch = __max(-limit, m_pitch);
m_pitch = __min(+limit, m_pitch);
// Keep longitude in sane range by wrapping.
if (m_yaw > XM_PI)
{
m_yaw -= XM_PI * 2.0f;
}
else if (m_yaw < -XM_PI)
{
m_yaw += XM_PI * 2.0f;
}
break;
}
}
Ajout de la prise en charge tactile
Les contrôles tactiles conviennent bien pour prendre en charge les utilisateurs avec des tablettes. Ce jeu collecte les entrées tactiles en délimitant certaines zones de l’écran, chacune s’alignant sur des actions spécifiques du jeu. L’entrée tactile de ce jeu utilise trois zones.
Les commandes suivantes résument le comportement de notre contrôle tactile. Entrée utilisateur | Action :------- | :-------- Déplacer le rectangle | L’entrée tactile est convertie en un joystick virtuel où le mouvement vertical est converti en mouvement de position avant/arrière et le mouvement horizontal en mouvement de position gauche/droite. Rectangle de tir | Tirer une sphère. Toucher à l’extérieur du rectangle de mouvement et de tir | Changer la rotation (le tangage et le lacet) de la vue de la caméra.
MoveLookController vérifie l’ID de pointeur pour déterminer où l’événement s’est produit et effectue une des actions suivantes :
- Si l’événement PointerMoved s’est produit dans le rectangle de mouvement ou de tir, mettre à jour la position du pointeur pour le contrôleur.
- Si l’événement PointerMoved s’est produit ailleurs dans le reste de l’écran (défini comme étant les contrôles de vue), calculer le changements du tangage et du lacet du vecteur de direction de la vue.
Une fois que nous avons implémenté nos contrôles tactiles, les rectangles que nous avons dessinés précédemment en utilisant Direct2D vont indiquer aux joueurs où se trouvent les zones de mouvement, de tir et de vue.
Voyons maintenant comment nous implémentons chaque contrôle.
Contrôleur de mouvement et de tir
Le rectangle du contrôleur de mouvement dans le quadrant inférieur gauche de l’écran est utilisé comme pavé directionnel. Le fait de faire glisser votre pouce vers la gauche ou la droite dans cet espace déplace le joueur vers la gauche ou la droite, tandis que le faire glisser vers le haut ou le bas déplace la caméra vers l’avant ou vers l’arrière. Une fois que vous avez configuré ceci, le fait d’appuyer sur le contrôleur de tir dans le quadrant inférieur droit de l’écran tire une sphère.
Les méthodes SetMoveRect et SetFireRect créent nos rectangles d’entrée, en prenant deux vecteurs 2D pour spécifier les positions du coin supérieur gauche et du coin inférieur droit de chaque rectangle à l’écran.
Les paramètres sont ensuite affectés à m_fireUpperLeft et m_fireLowerRight, qui vont nous aider à déterminer si l’utilisateur touche à l’intérieur des rectangles.
m_fireUpperLeft = upperLeft;
m_fireLowerRight = lowerRight;
Si l’écran est redimensionné, ces rectangles sont redessinés à la taille appropriée.
Maintenant que nous avons délimité nos contrôles, il est temps de déterminer quand un utilisateur les utilise réellement. Pour cela, nous avons configuré certains gestionnaires d’événements dans la méthode MoveLookController::InitWindow pour quand l’utilisateur appuie, déplace ou relâche son pointeur.
window.PointerPressed({ this, &MoveLookController::OnPointerPressed });
window.PointerMoved({ this, &MoveLookController::OnPointerMoved });
window.PointerReleased({ this, &MoveLookController::OnPointerReleased });
Nous allons d’abord déterminer ce qui se passe quand l’utilisateur appuie d’abord dans les rectangles de mouvement ou de tir en utilisant la méthode OnPointerPressed. Nous vérifions ici où ils touchent un contrôle et si un pointeur se trouve déjà dans ce contrôleur. S’il s’agit du premier doigt à toucher le contrôle spécifique, voici ce que nous faisons.
- Stockez l’emplacement du touché dans m_moveFirstDown ou dans m_fireFirstDown sous forme de vecteur 2D.
- Affectez l’ID de pointeur à m_movePointerID ou à m_firePointerID.
- Définissez l’indicateur InUse approprié (m_moveInUse ou m_fireInUse) sur
true
, car nous avons maintenant un pointeur actif pour ce contrôle.
PointerPoint point = args.CurrentPoint();
uint32_t pointerID = point.PointerId();
Point pointerPosition = point.Position();
PointerPointProperties pointProperties = point.Properties();
auto pointerDevice = point.PointerDevice();
auto pointerDeviceType = pointerDevice.PointerDeviceType();
XMFLOAT2 position = XMFLOAT2(pointerPosition.X, pointerPosition.Y);
...
case MoveLookControllerState::Active:
switch (pointerDeviceType)
{
case winrt::Windows::Devices::Input::PointerDeviceType::Touch:
// Check to see if this pointer is in the move control.
if (position.x > m_moveUpperLeft.x &&
position.x < m_moveLowerRight.x &&
position.y > m_moveUpperLeft.y &&
position.y < m_moveLowerRight.y)
{
// If no pointer is in this control yet.
if (!m_moveInUse)
{
// Process a DPad touch down event.
// Save the location of the initial contact
m_moveFirstDown = position;
// Store the pointer using this control
m_movePointerID = pointerID;
// Set InUse flag to signal there is an active move pointer
m_moveInUse = true;
}
}
// Check to see if this pointer is in the fire control.
else if (position.x > m_fireUpperLeft.x &&
position.x < m_fireLowerRight.x &&
position.y > m_fireUpperLeft.y &&
position.y < m_fireLowerRight.y)
{
if (!m_fireInUse)
{
// Save the location of the initial contact
m_fireLastPoint = position;
// Store the pointer using this control
m_firePointerID = pointerID;
// Set InUse flag to signal there is an active fire pointer
m_fireInUse = true;
...
}
}
...
Maintenant que nous avons déterminé si l’utilisateur touche un contrôle de mouvement ou de tir, nous voyons si le joueur effectue des mouvements en appuyant avec son doigt. En utilisant la méthode MoveLookController::OnPointerMoved, nous vérifions quel pointeur a été déplacé, puis nous stockons sa nouvelle position sous forme de vecteur 2D.
PointerPoint point = args.CurrentPoint();
uint32_t pointerID = point.PointerId();
Point pointerPosition = point.Position();
PointerPointProperties pointProperties = point.Properties();
auto pointerDevice = point.PointerDevice();
// convert to allow math
XMFLOAT2 position = XMFLOAT2(pointerPosition.X, pointerPosition.Y);
switch (m_state)
{
case MoveLookControllerState::Active:
// Decide which control this pointer is operating.
// Move control
if (pointerID == m_movePointerID)
{
// Save the current position.
m_movePointerPosition = position;
}
// Look control
else if (pointerID == m_lookPointerID)
{
...
}
// Fire control
else if (pointerID == m_firePointerID)
{
m_fireLastPoint = position;
}
...
Une fois que l’utilisateur a effectué ses mouvements dans les contrôles, il libère le pointeur. En utilisant la méthode MoveLookController::OnPointerReleased, nous déterminons quel pointeur a été relâché, puis nous effectuons une série de réinitialisations.
Si le contrôle de mouvement a été relâché, voici ce que nous faisons.
- Définir la vélocité du joueur sur
0
dans toutes les directions, pour l’empêcher de se déplacer dans le jeu. - Basculer m_moveInUse sur
false
, car l’utilisateur ne touche plus le contrôleur de mouvement. - Définir l’ID du pointeur de mouvement sur,
0
, car il n’y a plus de pointeur dans le contrôleur de mouvement.
if (pointerID == m_movePointerID)
{
// Stop on release.
m_velocity = XMFLOAT3(0, 0, 0);
m_moveInUse = false;
m_movePointerID = 0;
}
Pour le contrôle de tir, s’il a été relâché, tout ce que nous faisons est de basculer l’indicateur m_fireInUse sur false
et l’ID du pointeur de tir sur 0
, car il n’y a plus de pointeur dans le contrôle de tir.
else if (pointerID == m_firePointerID)
{
m_fireInUse = false;
m_firePointerID = 0;
}
Contrôleur de vue
Nous traitons les événements de pointeur d’appareil tactile pour les régions inutilisées de l’écran comme le contrôleur de vue. Le fait de faire glisser votre doigt autour de cette zone change le tangage et le lacet (la rotation) de la caméra du joueur.
Si l’événement MoveLookController::OnPointerPressed est déclenché sur un appareil tactile de cette région et que l’état du jeu est défini sur Active, un ID de pointeur lui est affecté.
// If no pointer is in this control yet.
if (!m_lookInUse)
{
// Save point for later move.
m_lookLastPoint = position;
// Store the pointer using this control.
m_lookPointerID = pointerID;
// These are for smoothing.
m_lookLastDelta.x = m_lookLastDelta.y = 0;
m_lookInUse = true;
}
Ici, , MoveLookController affecte l’ID de pointeur pour le pointeur qui a déclenché l’événement à une variable spécifique qui correspond à la région de la vue. Dans le cas d’un touché dans la région de la vue, la variable m_lookPointerID est définie sur l’ID du pointeur qui a déclenché l’événement. Une variable booléenne, m_lookInUse, est également définie pour indiquer que le contrôle n’a pas encore été relâché.
Examinons maintenant comment l’exemple de jeu gère l’événement d’écran tactile PointerMoved.
Dans la méthode MoveLookController::OnPointerMoved, nous regardons quel type d’ID de pointeur a été affecté à l’événement. Si c’est m_lookPointerID, nous calculons le changement de position du pointeur. Nous utilisons ensuite ce delta pour calculer dans quelle mesure la rotation doit changer. Enfin, nous sommes à un point où nous pouvons mettre à jour m_pitch et m_yaw, qui doivent être utilisés dans le jeu pour changer la rotation du joueur.
// This is the look pointer.
else if (pointerID == m_lookPointerID)
{
// Look control.
XMFLOAT2 pointerDelta;
// How far did the pointer move?
pointerDelta.x = position.x - m_lookLastPoint.x;
pointerDelta.y = position.y - m_lookLastPoint.y;
XMFLOAT2 rotationDelta;
// Scale for control sensitivity.
rotationDelta.x = pointerDelta.x * MoveLookConstants::RotationGain;
rotationDelta.y = pointerDelta.y * MoveLookConstants::RotationGain;
// Save for next time through.
m_lookLastPoint = position;
// Update our orientation based on the command.
m_pitch -= rotationDelta.y;
m_yaw += rotationDelta.x;
// Limit pitch to straight up or straight down.
float limit = XM_PI / 2.0f - 0.01f;
m_pitch = __max(-limit, m_pitch);
m_pitch = __min(+limit, m_pitch);
...
}
La dernière chose que nous allons examiner est comment l’exemple de jeu gère l’événement d’écran tactile PointerReleased.
Une fois que l’utilisateur a terminé le mouvement tactile et retiré son doigt de l’écran, MoveLookController::OnPointerReleased est lancée.
Si l’ID du pointeur qui a déclenché l’événement PointerReleased est l’ID du pointeur de mouvement précédemment enregistré, MoveLookController définit la vélocité sur 0
, car cela signifie que le joueur a cessé de toucher la zone de vue.
else if (pointerID == m_lookPointerID)
{
m_lookInUse = false;
m_lookPointerID = 0;
}
Ajout de la prise en charge de la souris et du clavier
Ce jeu a la disposition des contrôles suivante pour le clavier et la souris.
Entrée utilisateur | Action |
---|---|
W | Déplacer le joueur vers l’avant |
A | Déplacer le joueur vers la gauche |
S | Déplacer le joueur vers l’arrière |
D | Déplacer le joueur vers la droite |
X | Déplacer la vue vers le haut |
Barre d’espace | Déplacer la vue vers le bas |
P | Mettre le jeu en pause |
Déplacement de la souris | Changer la rotation (le tangage et le lacet) de la vue de la caméra |
Bouton gauche de la souris | Tirer une sphère |
Pour utiliser le clavier, l’exemple de jeu inscrit deux nouveaux événements, CoreWindow::KeyUp et CoreWindow::KeyDown, dans la méthode MoveLookController::InitWindow. Ces événements gèrent l’appui et le relâchement d’une touche.
window.KeyDown({ this, &MoveLookController::OnKeyDown });
window.KeyUp({ this, &MoveLookController::OnKeyUp });
La souris est traitée un peu différemment des contrôles tactiles, même si elle utilise un pointeur. Pour s’aligner sur la disposition de notre contrôle, MoveLookController fait pivoter la caméra chaque fois que la souris est déplacée et se déclenche quand l’utilisateur appuie sur le bouton gauche de la souris.
Ceci est géré dans la méthode OnPointerPressed de MoveLookController.
Dans cette méthode, nous vérifions quel type de périphérique pointeur est utilisé avec l’énumérationWindows::Devices::Input::PointerDeviceType
.
Si le jeu est dans l’état Active et que PointerDeviceType n’est pas Touch (Tactile), nous supposons qu’il s’agit d’une entrée de la souris.
case MoveLookControllerState::Active:
switch (pointerDeviceType)
{
case winrt::Windows::Devices::Input::PointerDeviceType::Touch:
// Behavior for touch controls
...
default:
// Behavior for mouse controls
bool rightButton = pointProperties.IsRightButtonPressed();
bool leftButton = pointProperties.IsLeftButtonPressed();
if (!m_autoFire && (!m_mouseLeftInUse && leftButton))
{
m_firePressed = true;
}
if (!m_mouseInUse)
{
m_mouseInUse = true;
m_mouseLastPoint = position;
m_mousePointerID = pointerID;
m_mouseLeftInUse = leftButton;
m_mouseRightInUse = rightButton;
// These are for smoothing.
m_lookLastDelta.x = m_lookLastDelta.y = 0;
}
break;
}
break;
Quand le joueur cesse d’appuyer sur un des boutons de la souris, l’événement de souris CoreWindow::PointerReleased est déclenché, appelant la méthode MoveLookController::OnPointerReleased, et l’entrée est terminée. À ce stade, les sphères cesseront d’être tirées si le bouton gauche de la souris était enfoncé et qu’il est maintenant relâché. Comme la vue est toujours activée, le jeu continue d’utiliser le même pointeur de souris pour le suivi des événements de vue en cours.
case MoveLookControllerState::Active:
// Touch points
if (pointerID == m_movePointerID)
{
// Stop movement
...
}
else if (pointerID == m_lookPointerID)
{
// Stop look rotation
...
}
// Fire button has been released
else if (pointerID == m_firePointerID)
{
// Stop firing
...
}
// Mouse point
else if (pointerID == m_mousePointerID)
{
bool rightButton = pointProperties.IsRightButtonPressed();
bool leftButton = pointProperties.IsLeftButtonPressed();
// Mouse no longer in use so stop firing
m_mouseInUse = false;
// Don't clear the mouse pointer ID so that Move events still result in Look changes.
// m_mousePointerID = 0;
m_mouseLeftInUse = leftButton;
m_mouseRightInUse = rightButton;
}
break;
Examinons maintenant le dernier type de contrôle que nous allons prendre en charge : les boîtiers de commande. Les boîtiers de commande sont gérés séparément des contrôles tactiles et souris, car ils n’utilisent pas l’objet pointeur. Pour cette raison, quelques nouveaux gestionnaires d’événements et quelques méthodes devront être ajoutés.
Ajout de la prise en charge de la manette de jeu
Pour ce jeu, la prise en charge du boîtier de commande est ajoutée par des appels aux API Windows.Gaming.Input. Cet ensemble d’API permet d’accéder aux entrées du contrôleur de jeu, comme les volants de voiture de course et les manches d’avion.
Voici les contrôles de notre boîtier de commande.
Entrée utilisateur | Action |
---|---|
Stick analogique gauche | Déplacer le joueur |
Stick analogique droit | Changer la rotation (le tangage et le lacet) de la vue de la caméra |
Déclencheur droit | Tirer une sphère |
Bouton Démarrer/Menu | Mettre en pause ou reprendre le jeu |
Dans la méthode InitWindow, nous ajoutons deux nouveaux événements pour déterminer si un boîtier de commande a été ajouté ou supprimé. Ces événements mettent à jour la propriété m_gamepadsChanged. Ceci est utilisé dans la méthode UpdatePollingDevices pour vérifier si la liste des boîtiers de commande connus a changé.
// Detect gamepad connection and disconnection events.
Gamepad::GamepadAdded({ this, &MoveLookController::OnGamepadAdded });
Gamepad::GamepadRemoved({ this, &MoveLookController::OnGamepadRemoved });
Remarque
Les applications UWP ne peuvent pas recevoir d’entrée d’un contrôleur de jeu pendant que l’application n’a pas le focus.
La méthode UpdatePollingDevices
La méthode UpdatePollingDevices de l’instance MoveLookController vérifie immédiatement si un boîtier de commande est connecté. Si tel est le cas, nous allons commencer à lire son état avec Gamepad.GetCurrentReading. Ceci retourne le struct GamepadReading, ce qui nous permet de vérifier sur quels boutons l’utilisateur a cliqué ou quels joysticks il a fait bouger.
Si l’état du jeu est WaitForInput, nous écoutons seulement le bouton Démarrer/Menu du contrôleur, pour que le jeu puisse reprendre.
Si l’état est Active, nous vérifions l’entrée de l’utilisateur et nous déterminons quelle action doit se produire dans le jeu. Par exemple, si l’utilisateur a fait bouger le stick analogique gauche dans une direction spécifique, cela permet au jeu de savoir que nous devons déplacer le joueur dans la direction correspondant au mouvement du stick. Le mouvement du stick dans une direction spécifique doit s’inscrire dans une zone aussi grande que le rayon de la zone morte ; sinon, rien ne se produit. Le rayon de cette zone morte est nécessaire pour gérer les « effleurements », c’est-à-dire quand le contrôleur détecte des petits mouvements effectués par le pouce du joueur quand il est posé sur le stick. Sans zones mortes, les contrôles peuvent apparaître trop sensibles à l’utilisateur.
L’entrée du joystick est comprise entre -1 et 1 à la fois pour l’axe x et pour l’axe y. La constante suivante spécifie le rayon de la zone morte du bâton de pouce.
#define THUMBSTICK_DEADZONE 0.25f
En utilisant cette variable, nous allons ensuite commencer à traiter l’entrée du joystick actionnable. Le mouvement se produit avec une valeur de [-1, -0,26] ou [0,26, 1] sur l’un et l’autre axe.
Cette partie de la méthode UpdatePollingDevices gère les joysticks gauche et droit. Les valeurs X et Y de chaque stick sont vérifiées pour déterminer si elles se trouvent en dehors de la zone morte. Si l’une des deux ou les deux le sont, nous allons mettre à jour le composant correspondant. Par exemple, si le joystick gauche est déplacé à gauche le long de l’axe X, nous allons ajouter -1 au composant x du vecteur m_moveCommand. Ce vecteur est celui qui sera utilisé pour agréger tous les mouvements sur tous les périphériques et il sera ensuite utilisé pour calculer où le joueur doit se déplacer.
// Use the left thumbstick to control the eye point position
// (position of the player).
// Check if left thumbstick is outside of dead zone on x axis
if (reading.LeftThumbstickX > THUMBSTICK_DEADZONE ||
reading.LeftThumbstickX < -THUMBSTICK_DEADZONE)
{
// Get value of left thumbstick's position on x axis
float x = static_cast<float>(reading.LeftThumbstickX);
// Set the x of the move vector to 1 if the stick is being moved right.
// Set to -1 if moved left.
m_moveCommand.x -= (x > 0) ? 1 : -1;
}
// Check if left thumbstick is outside of dead zone on y axis
if (reading.LeftThumbstickY > THUMBSTICK_DEADZONE ||
reading.LeftThumbstickY < -THUMBSTICK_DEADZONE)
{
// Get value of left thumbstick's position on y axis
float y = static_cast<float>(reading.LeftThumbstickY);
// Set the y of the move vector to 1 if the stick is being moved forward.
// Set to -1 if moved backwards.
m_moveCommand.y += (y > 0) ? 1 : -1;
}
De la même façon que le stick gauche contrôle le mouvement, le stick droit contrôle la rotation de la caméra.
Le comportement du joystick droit s’aligne sur le comportement du mouvement de la souris dans notre configuration du contrôle de la souris et du clavier. Si le stick se trouve en dehors de la zone morte, nous calculons la différence entre la position actuelle du pointeur et l’emplacement où l’utilisateur tente maintenant de regarder. Cette modification de la position du pointeur (pointerDelta) est ensuite utilisée pour mettre à jour le tangage et le lacet de la rotation de la caméra, qui seront ensuite appliqués dans notre méthode Update. Le vecteur pointerDelta peut sembler familier, car il est également utilisé dans la méthode MoveLookController::OnPointerMoved pour suivre le changement de la position du pointeur pour nos entrées de souris et tactiles.
// Use the right thumbstick to control the look at position
XMFLOAT2 pointerDelta;
// Check if right thumbstick is outside of deadzone on x axis
if (reading.RightThumbstickX > THUMBSTICK_DEADZONE ||
reading.RightThumbstickX < -THUMBSTICK_DEADZONE)
{
float x = static_cast<float>(reading.RightThumbstickX);
// Register the change in the pointer along the x axis
pointerDelta.x = x * x * x;
}
// No actionable thumbstick movement. Register no change in pointer.
else
{
pointerDelta.x = 0.0f;
}
// Check if right thumbstick is outside of deadzone on y axis
if (reading.RightThumbstickY > THUMBSTICK_DEADZONE ||
reading.RightThumbstickY < -THUMBSTICK_DEADZONE)
{
float y = static_cast<float>(reading.RightThumbstickY);
// Register the change in the pointer along the y axis
pointerDelta.y = y * y * y;
}
else
{
pointerDelta.y = 0.0f;
}
XMFLOAT2 rotationDelta;
// Scale for control sensitivity.
rotationDelta.x = pointerDelta.x * 0.08f;
rotationDelta.y = pointerDelta.y * 0.08f;
// Update our orientation based on the command.
m_pitch += rotationDelta.y;
m_yaw += rotationDelta.x;
// Limit pitch to straight up or straight down.
m_pitch = __max(-XM_PI / 2.0f, m_pitch);
m_pitch = __min(+XM_PI / 2.0f, m_pitch);
Les contrôles du jeu ne seraient pas complets sans la possibilité de tirer des sphères !
Cette méthode UpdatePollingDevices vérifie également si l’utilisateur appuie sur le déclencheur de droite. Si c’est le cas, notre propriété m_firePressed est définie sur true, signalant au jeu que les sphères doivent commencer à être tirées.
if (reading.RightTrigger > TRIGGER_DEADZONE)
{
if (!m_autoFire && !m_gamepadTriggerInUse)
{
m_firePressed = true;
}
m_gamepadTriggerInUse = true;
}
else
{
m_gamepadTriggerInUse = false;
}
La méthode Update
Pour conclure, examinons plus en détail la méthode Update. Cette méthode fusionne tous les mouvements ou rotations effectués par le joueur avec n’importe quelle entrée prise en charge pour générer un vecteur de vélocité, et pour mettre à jour nos valeurs de tangage et de lacet pour que notre boucle de jeu puisse y accéder.
La méthode Update démarre en appelant UpdatePollingDevices pour mettre à jour l’état du contrôleur. Cette méthode collecte également toutes les entrées d’un boîtier de commande et ajoute ses mouvements au vecteur m_moveCommand.
Dans notre méthode Update, nous effectuons ensuite les vérifications des entrées suivantes.
- Si le joueur utilise le rectangle du contrôleur de mouvement, nous allons ensuite déterminer le changement de la position du pointeur et l’utiliser pour calculer si l’utilisateur a déplacé le pointeur en dehors de la zone morte du contrôleur. S’il est en dehors de la zone morte, la propriété m_moveCommand du vecteur est alors mise à jour avec la valeur du joystick virtuel.
- Si l’utilisateur appuie sur une des entrées du clavier de mouvement, une valeur
1.0f
ou-1.0f
est ajoutée dans le composant correspondant du vecteur m_moveCommand (1.0f
pour un mouvement vers l’avant et-1.0f
pour un mouvement vers l’arrière).
Une fois que toutes les entrées de mouvement ont été prises en compte, nous utilisons le vecteur m_moveCommand à travers certains calculs pour générer un nouveau vecteur qui représente la direction du joueur par rapport au monde du jeu.
Nous prenons ensuite nos mouvements par rapport au monde et nous les appliquons au joueur comme vélocité dans cette direction.
Enfin, nous réinitialisons le vecteur m_moveCommand sur (0.0f, 0.0f, 0.0f)
, pour que tout soit prêt pour la prochaine trame du jeu.
void MoveLookController::Update()
{
// Get any gamepad input and update state
UpdatePollingDevices();
if (m_moveInUse)
{
// Move control.
XMFLOAT2 pointerDelta;
pointerDelta.x = m_movePointerPosition.x - m_moveFirstDown.x;
pointerDelta.y = m_movePointerPosition.y - m_moveFirstDown.y;
// Figure out the command from the virtual joystick.
XMFLOAT3 commandDirection = XMFLOAT3(0.0f, 0.0f, 0.0f);
// Leave 32 pixel-wide dead spot for being still.
if (fabsf(pointerDelta.x) > 16.0f)
m_moveCommand.x -= pointerDelta.x / fabsf(pointerDelta.x);
if (fabsf(pointerDelta.y) > 16.0f)
m_moveCommand.y -= pointerDelta.y / fabsf(pointerDelta.y);
}
// Poll our state bits set by the keyboard input events.
if (m_forward)
{
m_moveCommand.y += 1.0f;
}
if (m_back)
{
m_moveCommand.y -= 1.0f;
}
if (m_left)
{
m_moveCommand.x += 1.0f;
}
if (m_right)
{
m_moveCommand.x -= 1.0f;
}
if (m_up)
{
m_moveCommand.z += 1.0f;
}
if (m_down)
{
m_moveCommand.z -= 1.0f;
}
// Make sure that 45deg cases are not faster.
if (fabsf(m_moveCommand.x) > 0.1f ||
fabsf(m_moveCommand.y) > 0.1f ||
fabsf(m_moveCommand.z) > 0.1f)
{
XMStoreFloat3(&m_moveCommand, XMVector3Normalize(XMLoadFloat3(&m_moveCommand)));
}
// Rotate command to align with our direction (world coordinates).
XMFLOAT3 wCommand;
wCommand.x = m_moveCommand.x * cosf(m_yaw) - m_moveCommand.y * sinf(m_yaw);
wCommand.y = m_moveCommand.x * sinf(m_yaw) + m_moveCommand.y * cosf(m_yaw);
wCommand.z = m_moveCommand.z;
// Scale for sensitivity adjustment.
// Our velocity is based on the command. Y is up.
m_velocity.x = -wCommand.x * MoveLookConstants::MovementGain;
m_velocity.z = wCommand.y * MoveLookConstants::MovementGain;
m_velocity.y = wCommand.z * MoveLookConstants::MovementGain;
// Clear movement input accumulator for use during next frame.
m_moveCommand = XMFLOAT3(0.0f, 0.0f, 0.0f);
}
Étapes suivantes
Maintenant que nous avons ajouté nos contrôles, nous devons ajouter une autre fonctionnalité pour créer un jeu immersif : le son ! La musique et les effets sonores étant un élément important dans tous les jeux, voyons maintenant l’ajout du son.