Dela via


Hantering av spelflöde

Anmärkning

Det här avsnittet är en del av Skapa ett enkelt UWP-spel (Universal Windows Platform) med DirectX självstudieserie. Ämnet på länken anger kontexten för serien.

Spelet har nu ett fönster, har registrerat vissa händelsehanterare och har läst in tillgångar asynkront. Det här avsnittet beskriver användningen av speltillstånd, hur du hanterar specifika nyckelspelstillstånd och hur du skapar en uppdateringsloop för spelmotorn. Sedan får vi lära oss mer om användargränssnittsflödet och slutligen förstå mer om de händelsehanterare som behövs för ett UWP-spel.

Speltillstånd som används för att hantera spelflöde

Vi använder speltillstånd för att hantera flödet av spelet.

När Simple3DGameDX provspel körs för första gången på en dator är det i ett tillstånd där inget spel har startats. Efterföljande gånger spelet körs kan det vara i något av dessa tillstånd.

  • Inget spel har startats, eller så är spelet mellan nivåerna (den höga poängen är noll).
  • Spelslingan körs och befinner sig mitt i en nivå.
  • Spelslingan körs inte på grund av att ett spel har slutförts (den höga poängen har ett värde som inte är noll).

Ditt spel kan ha så många tillstånd som det behöver. Men kom ihåg att det kan avslutas när som helst. Och när den återupptas förväntar sig användaren att den återupptas i det tillstånd den befann sig i när den avslutades.

Speltillståndshantering

Så under spelets initiering måste du stödja kallstart av spelet och återuppta spelet efter att ha stoppat det under flygning. Simple3DGameDX--exemplet sparar alltid sitt speltillstånd för att ge intryck av att det aldrig slutade.

Som svar på en paushändelse pausas spelet, men spelets resurser finns fortfarande i minnet. På samma sätt hanteras återupptagandehändelsen för att säkerställa att exempelspelet hämtas i det tillstånd det befann sig i när det sattes på paus eller avbröts. Beroende på tillståndet visas olika alternativ för spelaren.

  • Om spelet återupptas på mittennivå visas det pausat och överlägget erbjuder alternativet att fortsätta.
  • Om spelet återupptas i ett tillstånd där spelet har slutförts visar det höga poäng och ett alternativ för att spela ett nytt spel.
  • Slutligen, om spelet återupptas innan en nivå har startats, visar överlägget ett startalternativ för användaren.

Exempelspelet gör ingen åtskillnad på om spelet gör en kallstart, startar för första gången utan en suspenderingshändelse eller återupptas från ett suspenderat tillstånd. Det här är rätt design för alla UWP-appar.

I det här exemplet sker initieringen av speltillstånden i GameMain::InitializeGameState (en disposition av metoden visas i nästa avsnitt).

Här är ett flödesschema som hjälper dig att visualisera flödet. Den omfattar både initiering och uppdateringsloopen.

  • Initieringen börjar vid noden Starta när du söker efter aktuellt speltillstånd. Spelkod finns i GameMain::InitializeGameState i nästa avsnitt.

huvudmaskinen för vårt spel

GameMain::InitializeGameState-metoden

GameMain::InitializeGameState-metoden anropas indirekt via GameMain-klassens konstruktor, vilket är resultatet av att göra en GameMain--instans i 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();
}

Uppdatera spelmotorn

Metoden App::Run anropar GameMain::Run. Inom GameMain::Run finns en grundläggande tillståndsmaskin som hanterar alla större åtgärder som en användare kan vidta. Den högsta nivån på den här tillståndsdatorn handlar om att läsa in ett spel, spela en viss nivå eller fortsätta en nivå efter att spelet har pausats (av systemet eller av användaren).

I exempelspelet finns det tre huvudtillstånd (representerade av UpdateEngineState enumeration) som spelet kan befinna sig i.

  1. UpdateEngineState::WaitingForResources. Spelslingan upprepar sig och kan inte gå vidare förrän resurser (särskilt grafiska resurser) är tillgängliga. När asynkrona resursinläsningsuppgifter har slutförts uppdaterar vi tillståndet till UpdateEngineState::ResourcesLoaded. Detta sker vanligtvis mellan nivåer när nivån läser in nya resurser från disken, från en spelserver eller från en molnserverdel. I exempelspelet simulerar vi det här beteendet eftersom exemplet inte behöver några ytterligare resurser per nivå vid den tidpunkten.
  2. UpdateEngineState::WaitingForPress. Spelslingan är i en loop och väntar på specifik användarinmatning. Den här inmatningen är en spelaråtgärd för att läsa in ett spel, starta en nivå eller fortsätta en nivå. Exempelkoden refererar till dessa undertillstånd via PressResultState uppräkning.
  3. UpdateEngineState::D ynamics. Spelslingan körs medan användaren spelar. Medan användaren spelar söker spelet efter 3 villkor som det kan övergå till:
  • GameState::TimeExpired. Tidsgränsen för en nivå upphör att gälla.
  • GameState::LevelComplete. Spelarens slutförande av en nivå.
  • GameState::GameComplete. Spelarens slutförande av alla nivåer.

Ett spel är helt enkelt en tillståndsdator som innehåller flera mindre tillståndsdatorer. Varje specifikt tillstånd måste definieras med mycket specifika kriterier. Övergångar från ett tillstånd till ett annat måste baseras på diskreta användarindata eller systemåtgärder (till exempel inläsning av grafikresurser).

När du planerar för ditt spel bör du överväga att ta fram hela spelflödet för att se till att du har åtgärdat alla möjliga åtgärder som användaren eller systemet kan vidta. Ett spel kan vara mycket komplicerat, så en tillståndsmaskin är ett kraftfullt verktyg som hjälper dig att visualisera den här komplexiteten och göra den mer hanterbar.

Nu ska vi ta en titt på koden för uppdateringsloopen.

Uppdateringsmetoden GameMain::Update

Det här är strukturen för den tillståndsdator som används för att uppdatera spelmotorn.

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

Uppdatera användargränssnittet

Vi måste hålla spelaren uppdaterad om systemets tillstånd och tillåta att speltillståndet ändras beroende på spelarens åtgärder och de regler som definierar spelet. Många spel, inklusive det här exempelspelet, använder ofta användargränssnittselement (UI) för att presentera den här informationen för spelaren. Användargränssnittet innehåller representationer av speltillstånd och annan spelspecifik information, till exempel poäng, ammunition eller antalet återstående chanser. Användargränssnittet kallas också överlägg eftersom det återges separat från huvudgrafikpipelinen och placeras ovanpå 3D-projektionen.

En del UI-information presenteras också som en heads-up display (HUD) så att användaren kan se den informationen utan att ta ögonen helt bort från det huvudsakliga spelområdet. I exempelspelet skapar vi det här överlägget med hjälp av Direct2D-API:erna. Alternativt kan vi skapa det här överlägget med hjälp av XAML, som vi diskuterar i Utöka exempelspelet.

Det finns två komponenter i användargränssnittet.

  • HUD som innehåller poäng och information om spelets aktuella tillstånd.
  • Pausbilden, som är en svart rektangel med text överlagrad under spelets pausade/suspenderade tillstånd. Det här är spelöverlägget. Vi diskuterar det ytterligare i Lägga till ett användargränssnitt.

Föga förvånande har överlägget också en tillståndsdator. Överlägget kan visa en nivåstart eller ett överspelsmeddelande. Det är i princip en canvas där vi kan mata ut all information om spelets tillstånd som vi vill visa för spelaren medan spelet är pausat eller avbrutet.

Det renderade överlägget kan vara en av dessa sex skärmar, beroende på spelets tillstånd.

  1. Skärmen som visar förloppet för resursinläsning i början av spelet.
  2. Skärm för spelstatistik
  3. Skärmen för nivåstartmeddelande.
  4. Spelskärm när alla nivåer har slutförts utan att tiden tar slut.
  5. Skärm för när spelet är över och tiden tar slut.
  6. Pausa menyskärmen.

Genom att separera användargränssnittet från spelets grafikpipeline kan du arbeta med det oberoende av spelets grafikrenderingsmotor och minska komplexiteten i spelets kod avsevärt.

Så här strukturerar exempelspelet överläggets tillståndsdator.

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

Händelsehantering

Som vi såg i Definiera spelets UWP-appramverk avsnitt, registrerar många av visningsleverantörsmetoderna i App klassen händelsehanterare. Dessa metoder måste hantera dessa viktiga händelser korrekt innan vi lägger till spelmekanik eller startar grafikutveckling.

Korrekt hantering av händelserna i fråga är grundläggande för UWP-appupplevelsen. Eftersom en UWP-app när som helst kan aktiveras, inaktiveras, storleksändras, snappas upp, tas bort, pausas eller återupptas, måste spelet registrera sig för dessa händelser så snart det kan och hantera dem på ett sätt som håller upplevelsen smidig och förutsägbar för spelaren.

Det här är de händelsehanterare som används i det här exemplet och de händelser som de hanterar.

Händelsehanterare Beskrivning
OnActivated Hanterar CoreApplicationView::Aktiverad. Spelappen har förts till förgrunden, så huvudfönstret aktiveras.
OnDpiChanged Hanterar Graphics::Display::DisplayInformation::DpiChanged. DPI:et för skärmen har ändrats och spelet justerar sina resurser därefter.
ObsCoreWindow koordinater finns i enhetsoberoende bildpunkter (DIP:er) för Direct2D-. Därför måste du meddela Direct2D om ändringen i DPI för att visa alla 2D-tillgångar eller primitiver korrekt.
OnOrientationChanged Hanterar Graphics::Display::DisplayInformation::OrientationChanged. Skärmens orientering ändras och rendering behöver uppdateras.
VidSkärminnehållOgiltigförklaras Hanterar Graphics::Display::DisplayInformation::DisplayContentsInvalidated. Displayen kräver omritning och ditt spel måste renderas om.
OnResuming Hanterar CoreApplication::Återuppta. Spelappen återställer spelet från ett inaktiverat tillstånd.
OnSuspending Hanterar CoreApplication::Suspending. Spelappen sparar sitt tillstånd till disk. Den har 5 sekunder på sig att spara tillståndet i lagringen.
OnVisibilityChanged Hanterar CoreWindow::VisibilityChanged. Spelappen har ändrat synlighet och har antingen blivit synlig eller gjorts osynlig av en annan app som blir synlig.
NärFönsterAktiveringÄndras Behandlar CoreWindow::Aktiverad. Spelappens huvudfönster har inaktiverats eller aktiverats, så det måste ta bort fokus och pausa spelet eller återfå fokus. I båda fallen indikerar överlägget att spelet är pausat.
OnWindowClosed Hantering av CoreWindow::Closed. Spelappen stänger huvudfönstret och avbryter spelet.
OnWindowSizeChanged Hanterar CoreWindow::SizeChanged. Spelappen omallokerar grafikresurserna och överlägget för att anpassa storleksändringen och uppdaterar sedan återgivningsmålet.

Nästa steg

I det här avsnittet har vi sett hur det övergripande spelflödet hanteras med hjälp av speltillstånd och att ett spel består av flera olika tillståndsdatorer. Vi har också sett hur du uppdaterar användargränssnittet och hanterar händelsehanterare för nyckelappar. Nu är vi redo att gå in i renderingsloopen, spelet och dess mekanik.

Du kan gå igenom de återstående ämnena som dokumenterar det här spelet i valfri ordning.