Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Annotazioni
Questo argomento fa parte della serie di esercitazioni Creare un semplice gioco UWP (Universal Windows Platform) con DirectX. L'argomento in tale collegamento imposta il contesto per la serie.
Il gioco ora ha una finestra, ha registrato alcuni gestori di eventi e ha caricato gli asset in modo asincrono. Questo argomento illustra l'uso degli stati del gioco, come gestire stati specifici del gioco chiave e come creare un ciclo di aggiornamento per il motore di gioco. Si apprenderà quindi il flusso dell'interfaccia utente e, infine, si apprenderà di più sui gestori eventi necessari per un gioco UWP.
Stati del gioco usati per gestire il flusso di gioco
Usiamo gli stati del gioco per gestire il flusso del gioco.
Quando il gioco d'esempio Simple3DGameDX viene eseguito per la prima volta su un computer, si trova in uno stato in cui non è stato avviato alcun gioco. Nelle esecuzioni successive, il gioco può trovarsi in uno qualsiasi di questi stati.
- Nessun gioco è stato avviato o il gioco è compreso tra i livelli (il punteggio alto è zero).
- Il ciclo del gioco è in esecuzione ed è nel bel mezzo di un livello.
- Il ciclo di gioco non è in esecuzione perché un gioco è stato completato (il punteggio massimo ha un valore diverso da zero).
Il tuo gioco può avere tutti gli stati necessari. Ma ricorda che può essere terminato in qualsiasi momento. Quando riparte, l'utente si aspetta che riprenda nello stato in cui si trovava al momento dell'interruzione.
Gestione dello stato del gioco
Quindi, durante l'inizializzazione del gioco, dovrai supportare l'avvio a freddo del gioco e la ripresa dopo un'interruzione. L'esempio Simple3DGameDX salva sempre lo stato del gioco per far sembrare che non si sia mai fermato.
In risposta a un evento di sospensione, il gioco viene sospeso, ma le risorse del gioco sono ancora in memoria. Analogamente, l'evento di ripresa viene gestito per garantire che il gioco di esempio riprenda nello stato in cui si trovava quando è stato sospeso o terminato. A seconda dello stato, vengono presentate diverse opzioni al giocatore.
- Se il gioco riprende a metà di un livello, sembra essere in pausa e l'overlay offre la possibilità di continuare.
- Se il gioco riprende in uno stato in cui viene completato il gioco, visualizza i punteggi alti e un'opzione per giocare un nuovo gioco.
- Infine, se il gioco riprende prima dell'avvio di un livello, la sovrimpressione presenta un'opzione di avvio all'utente.
Il gioco di esempio non distingue se il gioco viene avviato a freddo, lanciato per la prima volta senza un evento di sospensione, o ripreso da uno stato sospeso. Questa è la progettazione corretta per qualsiasi app UWP.
In questo esempio, l'inizializzazione degli stati del gioco si verifica in GameMain::InitializeGameState (una struttura di tale metodo viene visualizzata nella sezione successiva).
Ecco un diagramma che aiuta a visualizzare il flusso. Illustra sia l'inizializzazione che il ciclo di aggiornamento.
- L'inizializzazione inizia al nodo Start quando si verifica lo stato corrente del gioco. Per il codice del gioco, vedi GameMain::InitializeGameState nella sezione successiva.
- Per ulteriori informazioni sul ciclo di aggiornamento, vai a Aggiornamento del motore di gioco. Per il codice del gioco, passare a GameMain::Update.
Metodo GameMain::InitializeGameState
metodo GameMain::InitializeGameState viene chiamato indirettamente tramite il costruttore della classe GameMain, che è il risultato di creare un'istanza GameMain all'interno di 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();
}
Aggiornare il motore di gioco
Il metodo App::Run chiama GameMain::Run. All'interno di GameMain::Run è una macchina a stati di base per la gestione di tutte le azioni principali che un utente può eseguire. Il livello più alto di questa macchina a stati riguarda il caricamento di un gioco, la riproduzione di un livello specifico o la continuazione di un livello dopo che il gioco è stato sospeso (dal sistema o dall'utente).
Nel gioco di esempio, ci sono 3 stati principali (rappresentati dall'enumerazione UpdateEngineState ) in cui il gioco può trovarsi.
- UpdateEngineState::WaitingForResources. Il ciclo del gioco sta ciclando, non è possibile eseguire la transizione fino a quando non sono disponibili risorse (in particolare quelle grafiche). Al termine delle attività di caricamento delle risorse asincrone, lo stato viene aggiornato UpdateEngineState::ResourcesLoaded. Ciò si verifica in genere tra i livelli quando il livello carica nuove risorse dal disco, da un server di gioco o da un back-end cloud. Nel gioco di esempio si simula questo comportamento, perché l'esempio non richiede risorse aggiuntive per livello in quel momento.
- UpdateEngineState::WaitingForPress. Il ciclo del gioco sta girando, in attesa di un input utente particolare. Questo input è un'azione del giocatore per caricare un gioco, avviare un livello o continuare un livello. Il codice di esempio fa riferimento a questi stati secondari tramite l'enumerazione PressResultState.
- UpdateEngineState::D ynamics. Il ciclo del gioco è in esecuzione con l'utente che gioca. Mentre l'utente sta giocando, il gioco verifica la presenza di 3 condizioni su cui può eseguire la transizione:
- GameState::TimeExpired. Scadenza del limite di tempo per un livello.
- GameState::LevelComplete. Completamento di un livello da parte del giocatore.
- GameState::GameComplete. Completamento di tutti i livelli dal giocatore.
Un gioco è semplicemente una macchina a stati contenente più macchine a stati più piccole. Ogni stato specifico deve essere definito da criteri molto specifici. Le transizioni da uno stato a un altro devono essere basate sull'input utente discreto o sulle azioni di sistema ,ad esempio il caricamento delle risorse grafiche.
Durante la pianificazione del gioco, valuta la possibilità di disegnare l'intero flusso di gioco per assicurarti di aver risolto tutte le possibili azioni che l'utente o il sistema può eseguire. Un gioco può essere molto complicato, quindi una macchina a stati è uno strumento potente per aiutarti a visualizzare questa complessità e renderla più gestibile.
Esaminiamo ora il codice per il ciclo di aggiornamento.
Il metodo GameMain::Update
Questa è la struttura della macchina a stati usata per aggiornare il motore di gioco.
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;
}
}
Aggiornare l'interfaccia utente
Dobbiamo mantenere il giocatore aggiornato allo stato del sistema e consentire allo stato del gioco di cambiare a seconda delle azioni del giocatore e delle regole che definiscono il gioco. Molti giochi, incluso questo gioco di esempio, usano in genere elementi dell'interfaccia utente per presentare queste informazioni al giocatore. L'interfaccia utente contiene rappresentazioni dello stato del gioco e altre informazioni specifiche del gioco, ad esempio punteggio, munizioni o il numero di probabilità rimanenti. L'interfaccia utente viene chiamata anche sovrimpressione perché viene eseguito il rendering separatamente dalla pipeline grafica principale e posizionata sopra la proiezione 3D.
Alcune informazioni sull'interfaccia utente vengono anche presentate come un display a testa in alto (HUD) per consentire all'utente di visualizzare tali informazioni senza togliere gli occhi completamente dall'area di gioco principale. Nel gioco di esempio creiamo questa sovrimpressione usando le API Direct2D. In alternativa, potremmo creare questa sovrimpressione usando XAML, che discutiamo nella sezione Estendere il gioco di esempio.
Esistono due componenti per l'interfaccia utente.
- HUD che contiene il punteggio e le informazioni sullo stato corrente del gioco.
- La bitmap di pausa, ovvero un rettangolo nero con testo sovrapposto durante lo stato di pausa/sospensione del gioco. Questa è l'interfaccia del gioco. Verrà illustrato più dettagliatamente in Aggiunta di un'interfaccia utente.
In modo imprevisto, anche la sovrimpressione ha una macchina a stati. La sovrimpressione può visualizzare un messaggio di inizio livello o di fine gioco. Si tratta essenzialmente di un'area di disegno su cui è possibile mostrare informazioni sullo stato del gioco che vogliamo visualizzare al giocatore mentre il gioco è messo in pausa o sospeso.
L'overlay visualizzato può essere una di queste sei schermate, a seconda dello stato del gioco.
- Schermata di stato del caricamento delle risorse all'inizio del gioco.
- Schermata statistiche del gioco.
- Schermata di avvio del messaggio di livello.
- Schermata di fine gioco quando si completano tutti i livelli senza che si esaurisca il tempo.
- Schermata di game-over quando il tempo scade.
- Sospendi la schermata del menu.
La separazione dell'interfaccia utente dalla pipeline grafica del gioco consente di lavorare in modo indipendente dal motore di rendering della grafica del gioco e riduce significativamente la complessità del codice del gioco.
Ecco come il gioco di esempio struttura la macchina degli stati dell'overlay.
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;
}
}
Gestione degli eventi
Come abbiamo visto nel Definisci il framework dell'app UWP del gioco argomento, molti dei metodi del provider di visualizzazioni dei gestori eventi app classe registrare. Questi metodi devono gestire correttamente questi eventi importanti prima di aggiungere meccanismi di gioco o avviare lo sviluppo della grafica.
La corretta gestione degli eventi in questione è fondamentale per l'esperienza dell'app UWP. Poiché un'app UWP può essere attivata, disattivata, ridimensionata, ancorata, disancorata, sospesa o ripresa, il gioco deve registrare questi eventi non appena possibile e gestirli per mantenere l'esperienza uniforme e prevedibile per il giocatore.
Questi sono i gestori eventi usati in questo esempio e gli eventi gestiti.
| Gestore eventi | Descrizione |
|---|---|
| OnActivated | Gestisce CoreApplicationView::Activated. L'app di gioco è stata portata in primo piano, quindi la finestra principale viene attivata. |
| OnDpiChanged | Gestisce Graphics::Display::DisplayInformation::DpiChanged. La DPI dello schermo è cambiata e il gioco regola le sue risorse di conseguenza.
Notale coordinate di CoreWindow sono in pixel indipendenti dal dispositivo (DIPs) per Direct2D. Di conseguenza, è necessario notificare a Direct2D la modifica in DPI per visualizzare correttamente qualsiasi asset o primitive 2D.
|
| QuandoOrientamentoCambiato | Gestisce Graphics::Display::DisplayInformation::OrientationChanged. L'orientamento della visualizzazione cambia e il rendering deve essere aggiornato. |
| QuandoContenutiDisplayInvalidati | Gestisce Graphics::Display::DisplayInformation::DisplayContentsInvalidated. È necessario ridisegnare lo schermo e il gioco deve essere nuovamente renderizzato. |
| OnResuming | Gestisce CoreApplication::Ripresa. L'app di gioco ripristina il gioco da uno stato sospeso. |
| OnSuspending | Gestisce CoreApplication::Suspending. L'app di gioco salva lo stato su disco. Ha 5 secondi per salvare lo stato su memoria di archiviazione. |
| OnVisibilityChanged | Gestisce CoreWindow::VisibilityChanged. L'app del gioco ha cambiato visibilità ed è diventata visibile o resa invisibile da un'altra app che diventa visibile. |
| SuCambiamentoAttivazioneFinestra | Gestisce CoreWindow::Activated. La finestra principale dell'app del gioco è stata o disattivata o attivata, quindi deve rimuovere lo stato attivo e sospendere il gioco, o riacquisire lo stato attivo. In entrambi i casi, la sovrimpressione indica che il gioco è in pausa. |
| OnWindowClosed | Gestisce CoreWindow::Closed. L'app del gioco chiude la finestra principale e sospende il gioco. |
| QuandoLaDimensioneDellaFinestraCambia | Gestisce il CoreWindow::SizeChanged. L'app di gioco rialloca le risorse grafiche e l'overlay per adattarsi alla variazione delle dimensioni e quindi aggiorna la destinazione di rendering. |
Passaggi successivi
In questo argomento abbiamo visto come il flusso generale del gioco viene gestito usando gli stati del gioco e che un gioco è costituito da più macchine a stati diversi. È stato anche illustrato come aggiornare l'interfaccia utente e gestire i gestori eventi dell'app chiave. Ora siamo pronti per approfondire il ciclo di rendering, il gioco e i suoi meccanismi.
È possibile esaminare gli argomenti rimanenti che documentano questo gioco in qualsiasi ordine.