Delen via


Spelverloopbeheer

Opmerking

Dit onderwerp maakt deel uit van de tutorialreeks Een eenvoudig Universal Windows Platform (UWP) spel maken met DirectX. In het onderwerp op die koppeling wordt de context voor de reeks ingesteld.

De game heeft nu een venster, heeft enkele gebeurtenis-handlers geregistreerd en heeft asynchroon assets geladen. In dit onderwerp wordt het gebruik van gamestatussen uitgelegd, hoe u specifieke belangrijke gamestatussen beheert en hoe u een updatelus maakt voor de game-engine. Vervolgens leert u meer over de gebruikersinterfacestroom en tot slot leert u meer over de gebeurtenis-handlers die nodig zijn voor een UWP-game.

Speltoestanden die worden gebruikt om het spelverloop te beheren

We maken gebruik van gamestatussen om het verloop van het spel te beheren.

Wanneer de Simple3DGameDX voorbeeldspelletje voor de eerste keer op een machine draait, bevindt het zich in de toestand waarin nog geen spel is gestart. Bij volgende keren dat het spel wordt uitgevoerd, kan het zich in een van deze staten bevinden.

  • Er is geen spel gestart of het spel ligt tussen niveaus (de hoge score is nul).
  • De game-loop draait en zit midden in een level.
  • De gamelus wordt niet uitgevoerd omdat een game is voltooid (de hoge score heeft een waarde die niet nul is).

Uw game kan zoveel statussen hebben als nodig is. Maar vergeet niet dat het op elk gewenst moment kan worden beëindigd. En wanneer het wordt hervat, verwacht de gebruiker dat deze wordt hervat in de status waarin deze zich bevond toen deze werd beëindigd.

Gamestatusbeheer

Dus tijdens de initialisatie van het spel moet u het spel koud starten ondersteunen en het spel hervatten nadat deze in de vlucht is gestopt. Het Simple3DGameDX-voorbeeld slaat altijd de gamestatus op om de indruk te geven dat het nooit is gestopt.

Als reactie op een onderbrekingsgebeurtenis wordt de gameplay onderbroken, maar de resources van de game zijn nog steeds in het geheugen. Op dezelfde manier wordt de hervattingsgebeurtenis afgehandeld zodat het voorbeeldspel weer wordt opgepakt in de status waarin het zich bevond toen het werd onderbroken of beëindigd. Afhankelijk van de status worden verschillende opties aan de speler gepresenteerd.

  • Als de game halverwege het niveau wordt hervat, lijkt het in pauzestand te staan en biedt de overlay de mogelijkheid om door te gaan.
  • Als het spel hervat in een staat waarin het spel is voltooid, worden de hoge scores en een optie weergegeven om een nieuw spel te spelen.
  • Als de game wordt hervat voordat een niveau is gestart, geeft de overlay een startoptie weer voor de gebruiker.

Het voorbeeldspel maakt geen onderscheid of het spel koud begint, voor het eerst wordt gestart zonder een suspend event, of wordt hervat vanuit een onderbroken staat. Dit is het juiste ontwerp voor elke UWP-app.

In dit voorbeeld vindt initialisatie van de gamestatussen plaats in GameMain::InitializeGameState (een overzicht van die methode wordt weergegeven in de volgende sectie).

Hier volgt een stroomdiagram om de stroom te visualiseren. Het omvat zowel initialisatie als de updatelus.

  • Initialisatie begint bij het startknooppunt wanneer u controleert op de huidige gamestatus. Zie GameMain::InitializeGameState in de volgende sectie voor gamecode.

de belangrijkste staatsmachine voor ons spel

De methode GameMain::InitializeGameState

GameMain::InitializeGameState methode wordt indirect aangeroepen via de constructor van de GameMain klasse, wat het resultaat is van het maken van een GameMain instantie binnen 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();
}

Game-engine bijwerken

De methode App::Run roept GameMain::Run aan. Binnen GameMain::Run is een eenvoudige statuscomputer voor het verwerken van alle belangrijke acties die een gebruiker kan uitvoeren. Het hoogste niveau van deze statusmachine gaat over het laden van een game, het spelen van een bepaald niveau of het voortzetten van een niveau nadat het spel is onderbroken (door het systeem of door de gebruiker).

In het voorbeeldspel zijn er drie belangrijke staten (vertegenwoordigd door de UpdateEngineState enum) waarin het spel zich kan bevinden.

  1. UpdateEngineState::WaitingForResources. De game-loop is aan het draaien en kan niet doorgaan totdat resources (met name grafische resources) beschikbaar zijn. Wanneer de asynchrone taken voor het laden van resources zijn voltooid, werken we de status bij naar UpdateEngineState::ResourcesLoaded. Dit gebeurt meestal tussen niveaus wanneer het niveau nieuwe resources laadt vanaf schijf, vanaf een gameserver of vanuit een back-end in de cloud. In het voorbeeldspel simuleren we dit gedrag, omdat het voorbeeld op dat moment geen extra resources per niveau nodig heeft.
  2. UpdateEngineState::WaitingForPress. De game-loop doorloopt en wacht op specifieke gebruikersinvoer. Deze invoer is een speleractie om een game te laden, een niveau te starten of een niveau voort te zetten. De voorbeeldcode verwijst naar deze substatussen via de opsomming PressResultState .
  3. UpdateEngineState::D ynamics. De spelcyclus draait terwijl de gebruiker speelt. Terwijl de gebruiker speelt, controleert het spel op 3 voorwaarden waarop het kan overstappen:
  • GameState::TimeExpired. Vervaldatum van de tijdslimiet voor een niveau.
  • GameState::LevelComplete. Voltooiing van een niveau door de speler.
  • GameState::GameComplete. Voltooiing van alle niveaus door de speler.

Een spel is gewoon een statusmachine met meerdere kleinere statusmachines. Elke specifieke status moet worden gedefinieerd met zeer specifieke criteria. Overgangen van de ene status naar de andere moeten zijn gebaseerd op discrete gebruikersinvoer of systeemacties (zoals het laden van grafische resources).

Bij het plannen van uw game, overweeg het volledige verloop van de game uit te tekenen zodat u zeker weet dat u alle mogelijke acties in overweging hebt genomen die de gebruiker of het systeem kan uitvoeren. Een game kan erg ingewikkeld zijn, dus een statusmachine is een krachtig hulpmiddel om u te helpen deze complexiteit te visualiseren en deze beter beheersbaar te maken.

Laten we eens kijken naar de code voor de updatelus.

De methode GameMain::Update

Dit is de structuur van de statusmachine die wordt gebruikt om de game-engine bij te werken.

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

De gebruikersinterface bijwerken

We moeten de speler op de hoogte houden van de status van het systeem en de gamestatus laten veranderen, afhankelijk van de acties van de speler en de regels die het spel definiëren. Veel games, waaronder dit voorbeeldspel, gebruiken vaak gebruikersinterface-elementen (UI) om deze informatie aan de speler te presenteren. De gebruikersinterface bevat weergaven van de gamestatus en andere play-specifieke informatie, zoals score, ammo of het aantal resterende kansen. De gebruikersinterface wordt ook wel de overlay genoemd omdat deze afzonderlijk van de hoofdpijplijn voor afbeeldingen wordt weergegeven en boven op de 3D-projectie wordt geplaatst.

Sommige UI-informatie wordt ook weergegeven als een hoofdweergave (HUD) zodat de gebruiker die informatie kan zien zonder dat de ogen volledig van het belangrijkste gameplay-gebied worden gehaald. In het voorbeeldspel maken we deze overlay met behulp van de Direct2D-API's. We kunnen deze overlay ook maken met behulp van XAML, die we bespreken in Het uitbreiden van het voorbeeldspel.

Er zijn twee onderdelen voor de gebruikersinterface.

  • De HUD die de score en informatie over de huidige status van gameplay bevat.
  • De pauze bitmap, een zwarte rechthoek met overgelegde tekst tijdens de gepauzeerde status van het spel. Dit is de overlay van het spel. We bespreken het verder in het toevoegen van een gebruikersinterface.

Het is niet verwonderlijk dat de overlay ook een statusmachine heeft. De overlay kan een startniveau- of game-over-bericht weergeven. Het is in wezen een canvas waarop we alle informatie over de spelstatus kunnen weergeven die we aan de speler willen tonen terwijl het spel gepauzeerd of onderbroken is.

De overlay kan een van deze zes schermen zijn, afhankelijk van de status van de game.

  1. Voortgangsscherm voor het laden van resources aan het begin van het spel.
  2. Scherm met statistieken voor gameplay.
  3. Startscherm voor niveaubericht.
  4. Game over-scherm wanneer alle niveaus zijn voltooid zonder dat de tijd opraakt.
  5. Scherm voor game-over wanneer de tijd opraakt.
  6. Menuscherm onderbreken.

Door uw gebruikersinterface te scheiden van de grafische pijplijn van uw game, kunt u er onafhankelijk van de graphics rendering-engine van het spel aan werken en de complexiteit van de code van uw game aanzienlijk verminderen.

Hier ziet u hoe het voorbeeldspel de statusmachine van de overlay structureert.

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

Gebeurtenisafhandeling

Zoals we zagen in het Het UWP app-framework van de game definiëren onderwerp, registreren veel van de methoden van de weergaveprovider van de App klasse gebeurtenis-handlers. Deze methoden moeten deze belangrijke gebeurtenissen correct verwerken voordat we gamemechanica toevoegen of grafische ontwikkeling starten.

De juiste verwerking van de betrokken gebeurtenissen is fundamenteel voor de UWP-app-ervaring. Omdat een UWP-app op elk gewenst moment kan worden geactiveerd, gedeactiveerd, verkleind, vastgemaakt, losgemaakt, onderbroken of hervat, moet de game zich zo snel mogelijk registreren voor deze gebeurtenissen en deze op een manier afhandelen die de ervaring soepel en voorspelbaar houdt voor de speler.

Dit zijn de gebeurtenis-handlers die in dit voorbeeld worden gebruikt en de gebeurtenissen die ze verwerken.

Gebeurtenishandler Beschrijving
OnActivated Verwerkt CoreApplicationView::Activated. De game-app is naar de voorgrond gebracht, dus het hoofdvenster wordt geactiveerd.
OnDpiChanged Verwerkt Graphics::Display::DisplayInformation::DpiChanged. De DPI van de weergave is gewijzigd en het spel past de resources dienovereenkomstig aan.
CoreWindow coördinaten bevinden zich in apparaatonafhankelijke pixels (DIPs) voor Direct2D-. Als gevolg hiervan moet u Direct2D op de hoogte stellen van de wijziging in DPI om 2D-assets of primitieven correct weer te geven.
OnOrientationChanged Verwerkt Graphics::Display::DisplayInformation::OrientationChanged. De oriëntatie van het scherm verandert en de weergave moet worden bijgewerkt.
BijWeergaveInhoudOngeldigverklaard Verwerkt Graphics::Display::DisplayInformation::DisplayContentsInvalidated. Voor de weergave moet opnieuw worden getekend en moet uw game opnieuw worden weergegeven.
OnResuming Verwerkt CoreApplication::Hervat. De game-app herstelt de game vanuit een onderbroken status.
OnSuspending Afhandelt CoreApplication::Suspending. De game-app slaat de status op schijf op. Het heeft vijf seconden om de status op te slaan in de opslag.
OnVisibilityChanged Verwerkt CoreWindow::VisibilityChanged. De zichtbaarheid van de game-app is gewijzigd, en is zichtbaar geworden of onzichtbaar gemaakt doordat een andere app zichtbaar werd.
OnWindowActivationChanged Verwerkt CoreWindow::Activated. Het hoofdvenster van de game-app is gedeactiveerd of geactiveerd, waardoor de focus moet worden verwijderd en de game pauzeren, of de focus herwinnen. In beide gevallen geeft de overlay aan dat het spel is onderbroken.
OnWindowClosed Verwerkt CoreWindow::Closed. De game-app sluit het hoofdvenster en onderbreekt het spel.
OnWindowSizeChanged Verwerkt CoreWindow::SizeChanged. De game-app herplaatst de grafische resources en overlay om de groottewijziging aan te passen en werkt vervolgens het renderdoel bij.

Volgende stappen

In dit onderwerp hebben we gezien hoe de algehele gamestroom wordt beheerd met behulp van gamestatussen en dat een game bestaat uit meerdere verschillende statusmachines. We hebben ook gezien hoe u de gebruikersinterface kunt bijwerken en hoe u gebeurtenis-handlers voor belangrijke apps beheert. Nu zijn we klaar om in de renderinglus, het spel en de mechanica ervan te duiken.

U kunt de resterende onderwerpen doorlopen die dit spel in elke volgorde documenteren.