Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Remarque
Cette rubrique fait partie de la série de tutoriels Créer un jeu simple pour la plateforme universelle Windows (UWP) avec DirectX. La rubrique à ce lien définit le contexte de la série.
Le jeu a maintenant une fenêtre, a enregistré certains gestionnaires d’événements et a chargé des ressources de manière asynchrone. Cette rubrique explique l’utilisation des états de jeu, comment gérer des états de jeu clés spécifiques et comment créer une boucle de mise à jour pour le moteur de jeu. Ensuite, nous allons découvrir le flux d’interface utilisateur et, enfin, en savoir plus sur les gestionnaires d’événements nécessaires pour un jeu UWP.
États de jeu utilisés pour gérer le flux de jeu
Nous utilisons des états de jeu pour gérer le flux du jeu.
Lorsque le Simple3DGameDX exemple de jeu s’exécute pour la première fois sur une machine, il est dans un état où aucun jeu n’a été démarré. Les fois suivantes où le jeu s'exécute, il peut être dans l'un de ces états.
- Aucun jeu n’a été démarré, ou le jeu est entre les niveaux (le score élevé est égal à zéro).
- La boucle de jeu est en train de tourner et est au milieu d’un niveau.
- La boucle de jeu ne s'exécute pas parce qu'un jeu a été terminé (car le score élevé a une valeur non nulle).
Votre jeu peut avoir autant d’états que nécessaire. Mais n’oubliez pas qu’il peut être arrêté à tout moment. Et quand il reprend, l’utilisateur s’attend à ce qu’il reprend dans l’état dans lequel il était terminé.
Gestion de l’état du jeu
Par conséquent, pendant l’initialisation du jeu, vous devrez prendre en charge le démarrage froid du jeu, ainsi que reprendre le jeu après l’avoir arrêté en vol. L’exemple Simple3DGameDX enregistre toujours son état de jeu afin de donner l’impression qu’il n’a jamais cessé.
En réponse à un événement de suspension, le gameplay est suspendu, mais les ressources du jeu sont toujours en mémoire. De même, l’événement de reprise est géré pour s’assurer que le jeu de démonstration reprenne dans l’état dans lequel il était lorsqu'il a été suspendu ou arrêté. Selon l’état, différentes options sont présentées au joueur.
- Si le jeu reprend en cours de niveau, il apparaît comme étant en pause, et l'interface offre la possibilité de continuer.
- Si le jeu reprend dans un état où le jeu est terminé, il affiche les scores élevés et une option pour jouer à un nouveau jeu.
- Enfin, si le jeu reprend avant le début d’un niveau, la superposition présente une option de démarrage à l’utilisateur.
L’exemple de jeu ne distingue pas si le jeu démarre à froid, est lancé pour la première fois sans événement de suspension préalable, ou reprend à partir d’un état suspendu. Il s’agit de la conception appropriée pour n’importe quelle application UWP.
Dans cet exemple, l’initialisation des états de jeu se produit dans GameMain ::InitializeGameState (un plan de cette méthode est affiché dans la section suivante).
Voici un organigramme pour vous aider à visualiser le flux. Il couvre à la fois l’initialisation et la boucle de mise à jour.
- L’initialisation commence au nœud Start lorsque vous vérifiez l’état actuel du jeu. Pour le code de jeu, consultez GameMain ::InitializeGameState dans la section suivante.
- Pour plus d’informations sur la boucle de mise à jour, accédez à Update game engine. Pour accéder au code du jeu, allez à GameMain::Update.
La méthode GameMain ::InitializeGameState
La méthode GameMain::InitializeGameState est appelée indirectement via le constructeur de la classe GameMain, ce qui résulte de la création d'une instance GameMain dans App::Load.
GameMain::GameMain(std::shared_ptr<DX::DeviceResources> const& deviceResources) : ...
{
m_deviceResources->RegisterDeviceNotify(this);
...
ConstructInBackground();
}
winrt::fire_and_forget GameMain::ConstructInBackground()
{
...
m_renderer->FinalizeCreateGameDeviceResources();
InitializeGameState();
...
}
void GameMain::InitializeGameState()
{
// Set up the initial state machine for handling Game playing state.
if (m_game->GameActive() && m_game->LevelActive())
{
// The last time the game terminated it was in the middle
// of a level.
// We are waiting for the user to continue the game.
...
}
else if (!m_game->GameActive() && (m_game->HighScore().totalHits > 0))
{
// The last time the game terminated the game had been completed.
// Show the high score.
// We are waiting for the user to acknowledge the high score and start a new game.
// The level resources for the first level will be loaded later.
...
}
else
{
// This is either the first time the game has run or
// the last time the game terminated the level was completed.
// We are waiting for the user to begin the next level.
...
}
m_uiControl->ShowGameInfoOverlay();
}
Mettre à jour le moteur de jeu
La méthode App ::Run appelle GameMain ::Run. Dans GameMain ::Run est un ordinateur d’état de base permettant de gérer toutes les actions majeures qu’un utilisateur peut entreprendre. Le niveau le plus élevé de cette machine d’état traite du chargement d’un jeu, du jeu d’un niveau spécifique ou de la poursuite d’un niveau une fois le jeu suspendu (par le système ou par l’utilisateur).
Dans l'exemple de jeu, il existe 3 états majeurs (représentés par l'énumération UpdateEngineState) dans lesquels le jeu peut être.
- UpdateEngineState ::WaitingForResources. La boucle de jeu tourne en répétition, incapable de passer à l'étape suivante tant que les ressources (en particulier les ressources graphiques) ne sont pas disponibles. Une fois les tâches de chargement de ressources asynchrones terminées, nous mettons à jour l’état sur UpdateEngineState ::ResourcesLoaded. Cela se produit généralement entre les niveaux lorsque le niveau charge de nouvelles ressources à partir du disque, d’un serveur de jeux ou d’un serveur principal cloud. Dans l’exemple de jeu, nous simulons ce comportement, car l’exemple n’a pas besoin de ressources supplémentaires par niveau à ce moment-là.
- UpdateEngineState ::WaitingForPress. La boucle de jeu se répète, en attente d’une entrée utilisateur spécifique. Cette entrée est une action de joueur pour charger un jeu, démarrer un niveau ou continuer un niveau. L’exemple de code fait référence à ces sous-états via l’énumération PressResultState.
- UpdateEngineState ::D ynamics. La boucle de jeu s'exécute alors que l'utilisateur joue. Pendant que l’utilisateur joue, le jeu vérifie 3 conditions sur lesquelles il peut passer :
- GameState ::TimeExpired. Expiration de la limite de temps pour un niveau.
- GameState : LevelComplete. Achèvement d’un niveau par le joueur.
- GameState ::GameComplete. Achèvement de tous les niveaux par le joueur.
Un jeu est simplement un ordinateur d’état contenant plusieurs machines d’état plus petites. Chaque état spécifique doit être défini par des critères très spécifiques. Les transitions d’un état à un autre doivent être basées sur une entrée utilisateur discrète ou des actions système (telles que le chargement de ressources graphiques).
Lors de la planification de votre jeu, envisagez de dessiner le flux complet du jeu pour vous assurer que vous avez pris en compte toutes les actions possibles que l’utilisateur ou le système peuvent prendre. Un jeu peut être très compliqué, donc un ordinateur d’état est un outil puissant pour vous aider à visualiser cette complexité et à le rendre plus gérable.
Examinons le code de la boucle de mise à jour.
Méthode GameMain :: Update
Il s’agit de la structure de l’ordinateur d’état utilisé pour mettre à jour le moteur de jeu.
void GameMain::Update()
{
// The controller object has its own update loop.
m_controller->Update();
switch (m_updateState)
{
case UpdateEngineState::WaitingForResources:
...
break;
case UpdateEngineState::ResourcesLoaded:
...
break;
case UpdateEngineState::WaitingForPress:
if (m_controller->IsPressComplete())
{
...
}
break;
case UpdateEngineState::Dynamics:
if (m_controller->IsPauseRequested())
{
...
}
else
{
// When the player is playing, work is done by Simple3DGame::RunGame.
GameState runState = m_game->RunGame();
switch (runState)
{
case GameState::TimeExpired:
...
break;
case GameState::LevelComplete:
...
break;
case GameState::GameComplete:
...
break;
}
}
if (m_updateState == UpdateEngineState::WaitingForPress)
{
// Transitioning state, so enable waiting for the press event.
m_controller->WaitForPress(
m_renderer->GameInfoOverlayUpperLeft(),
m_renderer->GameInfoOverlayLowerRight());
}
if (m_updateState == UpdateEngineState::WaitingForResources)
{
// Transitioning state, so shut down the input controller
// until resources are loaded.
m_controller->Active(false);
}
break;
}
}
Mettre à jour l’interface utilisateur
Nous devons garder le joueur informé de l’état du système et permettre à l’état du jeu de changer en fonction des actions du joueur et des règles qui définissent le jeu. De nombreux jeux, y compris cet exemple de jeu, utilisent généralement des éléments d’interface utilisateur pour présenter ces informations au joueur. L’interface utilisateur contient des représentations de l’état du jeu et d’autres informations spécifiques au jeu telles que le score, l’ammo ou le nombre de chances restantes. L’interface utilisateur est également appelée superposition, car elle est rendue séparément du pipeline graphique principal et placée sur la projection 3D.
Certaines informations d’interface utilisateur sont également présentées sous la forme d’un affichage tête-haut (HUD) pour permettre à l’utilisateur de voir ces informations sans prendre leurs yeux entièrement hors de la zone de jeu principale. Dans l’exemple de jeu, nous créons cette superposition à l’aide des API Direct2D. Vous pouvez également créer cette superposition à l’aide de XAML, que nous abordons dans Extension de l’exemple de jeu.
Il existe deux composants à l’interface utilisateur.
- HUD qui contient le score et les informations sur l’état actuel du jeu.
- Le bitmap de pause, qui est un rectangle noir avec du texte superposé, est utilisé lorsque le jeu est en pause/suspendu. Voici l'interface de jeu. Nous l’avons abordé plus en détail dans Ajout d’une interface utilisateur.
Sans surprise, la couche de superposition a également une machine d'état. La superposition peut afficher un début de niveau ou un message de jeu. Il s'agit essentiellement d'un fond sur lequel nous pouvons afficher des informations sur l'état du jeu que nous voulons montrer au joueur pendant que le jeu est en pause ou en suspension.
La superposition rendue peut être l’un de ces six écrans, en fonction de l’état du jeu.
- Écran de progression du chargement des ressources au début du jeu.
- Écran des statistiques de gameplay.
- Écran d'affichage du message de début de niveau.
- ** Écran de fin de jeu lorsque tous les niveaux sont terminés sans que le temps ne s'écoule.
- Écran de fin de partie lorsque le temps s’est écoulé.
- Écran du menu de pause.
La séparation de votre interface utilisateur du pipeline graphique de votre jeu vous permet de travailler indépendamment du moteur de rendu graphique du jeu et réduit considérablement la complexité du code de votre jeu.
Voici comment l’exemple de jeu structure l’ordinateur d’état de la superposition.
void GameMain::SetGameInfoOverlay(GameInfoOverlayState state)
{
m_gameInfoOverlayState = state;
switch (state)
{
case GameInfoOverlayState::Loading:
m_uiControl->SetGameLoading(m_loadingCount);
break;
case GameInfoOverlayState::GameStats:
...
break;
case GameInfoOverlayState::LevelStart:
...
break;
case GameInfoOverlayState::GameOverCompleted:
...
break;
case GameInfoOverlayState::GameOverExpired:
...
break;
case GameInfoOverlayState::Pause:
...
break;
}
}
Gestion des événements
Comme nous l'avons vu dans la rubrique Définir l'infrastructure d'application UWP du jeu, de nombreuses méthodes du fournisseur de vue de la classe App inscrivent des gestionnaires d'événements. Ces méthodes doivent gérer correctement ces événements importants avant d’ajouter des mécanismes de jeu ou de démarrer le développement graphique.
La gestion appropriée des événements en question est fondamentale pour l’expérience de l’application UWP. Étant donné qu’une application UWP peut à tout moment être activée, désactivée, redimensionnée, ancrée, désancrée, suspendue ou reprise, le jeu doit s’inscrire à ces événements aussitôt que possible et les gérer de manière à assurer une expérience fluide et prévisible pour le joueur.
Il s’agit des gestionnaires d’événements utilisés dans cet exemple et des événements qu’ils gèrent.
Gestionnaire d’événements | Descriptif |
---|---|
OnActivated | Gère CoreApplicationView::Activated. L’application de jeu a été apportée au premier plan, de sorte que la fenêtre principale est activée. |
OnDpiChanged | Gère Graphics::Display::DisplayInformation::DpiChanged. La résolution en PPP de l'affichage a changé, et le jeu ajuste ses ressources en conséquence.
RemarqueLes coordonnées CoreWindow sont en pixels indépendants du périphérique (DIPs) pour Direct2D. Par conséquent, vous devez notifier Direct2D de la modification de ppp pour afficher correctement les ressources ou primitives 2D.
|
OnOrientationChanged | Gère Graphics::Display::DisplayInformation::OrientationChanged. L’orientation des modifications d’affichage et du rendu doit être mise à jour. |
InvalidationContenuAffiché | Gère Graphics::Display::DisplayInformation::DisplayContentsInvalidated. L’affichage nécessite un redessinage et votre jeu doit être rendu à nouveau. |
OnResuming | Gère de CoreApplication : Reprise. L’application de jeu restaure le jeu à partir d’un état suspendu. |
OnSuspending | Gère CoreApplication::Suspending. L’application de jeu enregistre son état sur le disque. Il a 5 secondes pour enregistrer l’état dans la mémoire. |
OnVisibilityChanged | Gère CoreWindow::VisibilityChanged. L'application de jeu a changé de visibilité et est devenue visible ou invisible du fait qu'une autre application est devenue visible. |
OnWindowActivationChanged | Gère CoreWindow::Activated. La fenêtre principale de l’application de jeu a été désactivée ou activée. Elle doit donc supprimer le focus et suspendre le jeu, ou reprendre le focus. Dans les deux cas, la superposition indique que le jeu est suspendu. |
OnWindowClosed | Gérer CoreWindow::Fermé. L’application de jeu ferme la fenêtre principale et suspend le jeu. |
OnWindowSizeChanged | Traite CoreWindow::SizeChanged. L’application de jeu réalloue les ressources graphiques et la superposition pour prendre en charge le changement de taille, puis met à jour la cible de rendu. |
Étapes suivantes
Dans cette rubrique, nous avons vu comment le flux de jeu global est géré à l’aide des états de jeu et qu’un jeu est constitué de plusieurs machines d’état différentes. Nous avons également vu comment mettre à jour l’interface utilisateur et gérer les gestionnaires d’événements d’application clés. Maintenant, nous sommes prêts à plonger dans la boucle de rendu, le jeu et ses mécanismes.
Vous pouvez parcourir les rubriques restantes qui documentent ce jeu dans n’importe quel ordre.