Freigeben über


Marble Maze-Anwendungsstruktur

Die Struktur einer DirectX-App für die universelle Windows-Plattform (UWP) unterscheidet sich von der einer herkömmlichen Desktopanwendung. Anstatt mit Handletypen wie HWND und Funktionen wie CreateWindow zu arbeiten, stellt die Windows-Runtime Schnittstellen wie Windows::UI::Core::ICoreWindow bereit, sodass Sie UWP-Apps auf modernere, objektorientierte Weise entwickeln können. In diesem Abschnitt der Dokumentation wird gezeigt, wie der Marble Maze-App-Code strukturiert ist.

Hinweis

Der Beispielcode, der diesem Dokument entspricht, befindet sich im DirectX Marble Maze-Spielbeispiel.

Im Folgenden sind einige der wichtigsten Punkte aufgeführt, die in diesem Dokument erläutert werden, wenn Sie den Spielcode strukturieren:

  • Richten Sie in der Initialisierungsphase die von Ihrem Spiel benötigten Laufzeit- und Bibliothekskomponenten ein, und laden Sie spielspezifische Ressourcen.
  • UWP-Apps müssen innerhalb von 5 Sekunden nach dem Start mit der Verarbeitung von Ereignissen beginnen. Laden Sie daher nur wichtige Ressourcen, wenn Sie Ihre App laden. Spiele sollten große Ressourcen im Hintergrund laden und einen Statusbildschirm anzeigen.
  • Reagieren Sie in der Spielschleife auf Windows-Ereignisse, lesen Sie benutzereingaben, aktualisieren Sie Szenenobjekte, und rendern Sie die Szene.
  • Verwenden Sie Ereignishandler, um auf Fensterereignisse zu reagieren. (Diese ersetzen die Fenstermeldungen aus Desktop-Windows-Anwendungen.)
  • Verwenden Sie eine Zustandsmaschine, um den Fluss und die Reihenfolge der Spiellogik zu steuern.

Dateiorganisation

Einige der Komponenten in Marble Maze können mit jedem Spiel mit wenig oder ohne Änderungen wiederverwendet werden. Für Ihr eigenes Spiel können Sie die Organisation und Ideen anpassen, die diese Dateien bereitstellen. In der folgenden Tabelle werden die wichtigen Quellcodedateien kurz beschrieben.

Dateien BESCHREIBUNG
App.h, App.cpp Definiert die App- und DirectXApplicationSource Klassen, die die Ansicht (Fenster, Thread und Ereignisse) der App kapseln.
Audio.h, Audio.cpp Definiert die Audioklasse , die Audioressourcen verwaltet.
BasicLoader.h, BasicLoader.cpp Definiert die BasicLoader-Klasse , die Hilfsmethoden bereitstellt, mit denen Sie Texturen, Gitter und Shader laden können.
BasicMath.h Definiert Strukturen und Funktionen, mit denen Sie mit Vektor- und Matrixdaten und Berechnungen arbeiten können. Viele dieser Funktionen sind mit HLSL-Shadertypen kompatibel.
BasicReaderWriter.h, BasicReaderWriter.cpp Definiert die BasicReaderWriter-Klasse , die die Windows-Runtime zum Lesen und Schreiben von Dateidaten in einer UWP-App verwendet.
BasicShapes.h, BasicShapes.cpp Definiert die BasicShapes-Klasse , die Hilfsmethoden zum Erstellen grundlegender Formen wie Würfel und Kugeln bereitstellt. (Diese Dateien werden von der Marble Maze-Implementierung nicht verwendet).
Camera.h, Camera.cpp Definiert die Klasse Camera, die die Position und Ausrichtung einer Kamera bereitstellt.
Collision.h, Collision.cpp Verwaltet Kollisionsdaten zwischen der Murmel und anderen Objekten, z. B. dem Labyrinth.
DDSTextureLoader.h, DDSTextureLoader.cpp Definiert die CreateDDSTextureFromMemory-Funktion , die Texturen lädt, die sich im .dds Format aus einem Speicherpuffer befinden.
DirectXHelper.h Definiert DirectX-Hilfsfunktionen, die für viele DirectX-UWP-Apps nützlich sind.
LoadScreen.h, LoadScreen.cpp Definiert die LoadScreen-Klasse , die während der App-Initialisierung einen Ladebildschirm anzeigt.
MarbleMazeMain.h, MarbleMazeMain.cpp Definiert die MarbleMazeMain-Klasse , die spielspezifische Ressourcen verwaltet und einen Großteil der Spiellogik definiert.
MediaStreamer.h, MediaStreamer.cpp Definiert die MediaStreamer-Klasse , die Media Foundation verwendet, um das Spiel beim Verwalten von Audioressourcen zu unterstützen.
PersistentState.h, PersistentState.cpp Definiert die PersistentState-Klasse , die primitive Datentypen aus und in einen Sicherungsspeicher liest und schreibt.
Physics.h, Physics.cpp Definiert die Physics Klasse, die die physikalische Simulation zwischen der Murmel und dem Labyrinth implementiert.
Primitives.h Definiert geometrische Typen, die vom Spiel verwendet werden.
SampleOverlay.h, SampleOverlay.cpp Definiert die SampleOverlay Klasse, die allgemeine 2D- und Benutzeroberflächendaten und -vorgänge bereitstellt.
SDKMesh.h, SDKMesh.cpp Definiert die SDKMesh-Klasse , die Gitter lädt und rendert, die im SDK Mesh-Format (.sdkmesh) vorliegen.
StepTimer.h Definiert die StepTimer Klasse, die eine einfache Möglichkeit zum Abrufen von Gesamt- und verstrichenen Zeiten bietet.
UserInterface.h, UserInterface.cpp Definiert Funktionen, die sich auf die Benutzeroberfläche beziehen, z. B. das Menüsystem und die Highscore-Tabelle.

 

Entwurfszeit im Vergleich zu Laufzeitressourcenformaten

Verwenden Sie bei Bedarf Laufzeitformate anstelle von Entwurfszeitformaten, um Spielressourcen effizienter zu laden.

Ein -Entwurfszeitformat, ein-Format, ist das Format, das Sie beim Entwerfen Ihrer Ressource verwenden. In der Regel arbeiten 3D-Designer mit Entwurfszeitformaten. Einige Entwurfszeitformate sind auch textbasiert, sodass Sie sie in einem beliebigen textbasierten Editor ändern können. Entwurfszeitformate können ausführlich sein und mehr Informationen enthalten, als für Ihr Spiel erforderlich sind. Ein Laufzeitformat Format ist das binärformat, das von Ihrem Spiel gelesen wird. Laufzeitformate sind in der Regel kompakter und effizienter zu laden als die entsprechenden Entwurfszeitformate. Aus diesem Grund verwenden die meisten Spiele laufzeitbezogene Ressourcen zur Laufzeit.

Obwohl Ihr Spiel direkt ein Entwurfszeitformat lesen kann, gibt es mehrere Vorteile, ein separates Laufzeitformat zu verwenden. Da Laufzeitformate häufig kompakter sind, benötigen sie weniger Speicherplatz und benötigen weniger Zeit für die Übertragung über ein Netzwerk. Außerdem werden Laufzeitformate häufig als speicherzugeordnete Datenstrukturen dargestellt. Daher können sie viel schneller in den Arbeitsspeicher geladen werden, als z. B. eine XML-basierte Textdatei. Da separate Laufzeitformate in der Regel binärcodiert sind, sind sie für den Endbenutzer schwieriger zu ändern.

HLSL-Shader sind ein Beispiel für Ressourcen, die unterschiedliche Entwurfszeit- und Laufzeitformate verwenden. Marble Maze verwendet .hlsl als Entwurfszeitformat und .cso als Laufzeitformat. Eine .hlsl-Datei enthält Quellcode für den Shader; eine .cso-Datei enthält den entsprechenden Shaderbytecode. Wenn Sie HLSL-Dateien offline konvertieren und CSO-Dateien für Ihr Spiel bereitstellen, müssen Sie beim Laden des Spiels keine HLSL-Quelldateien in Bytecode konvertieren.

Aus Anweisungsgründen enthält das Marble Maze-Projekt sowohl das Entwurfszeitformat als auch das Laufzeitformat für viele Ressourcen, Sie müssen jedoch nur die Entwurfszeitformate im Quellprojekt für Ihr eigenes Spiel verwalten, da Sie sie bei Bedarf in Laufzeitformate konvertieren können. In dieser Dokumentation wird gezeigt, wie Sie die Entwurfszeitformate in die Laufzeitformate konvertieren.

Anwendungslebenszyklus

Marble Maze folgt dem Lebenszyklus einer typischen UWP-App. Weitere Informationen zum Lebenszyklus einer UWP-App finden Sie im App-Lebenszyklus.

Wenn ein UWP-Spiel initialisiert wird, initialisiert es in der Regel Laufzeitkomponenten wie Direct3D, Direct2D und alle von ihr genutzten Eingabe-, Audio- oder Physikbibliotheken. Außerdem werden spielspezifische Ressourcen geladen, die vor beginn des Spiels erforderlich sind. Diese Initialisierung erfolgt einmal während einer Spielsitzung.

In der Regel führen Spiele nach der Initialisierung die Spielschleifeaus. In dieser Schleife führen Spiele in der Regel vier Aktionen aus: Verarbeiten von Windows-Ereignissen, Sammeln von Eingaben, Aktualisieren von Szenenobjekten und Rendern der Szene. Wenn das Spiel die Szene aktualisiert, kann er den aktuellen Eingabezustand auf die Szenenobjekte anwenden und physische Ereignisse simulieren, z. B. Objektkonflikte. Das Spiel kann auch andere Aktivitäten ausführen, z. B. Soundeffekte wiedergeben oder Daten über das Netzwerk senden. Wenn das Spiel die Szene rendert, erfasst es den aktuellen Zustand der Szene und zeichnet sie auf das Anzeigegerät. In den folgenden Abschnitten werden diese Aktivitäten ausführlicher beschrieben.

Hinzufügen zur Vorlage

Die Vorlage DirectX 11-App (Universelle Windows-App) erstellt ein Kernfenster, in dem Sie mit Direct3D rendern können. Die Vorlage enthält auch die DeviceResources-Klasse , die alle Direct3D-Geräteressourcen erstellt, die zum Rendern von 3D-Inhalten in einer UWP-App erforderlich sind.

Die App Klasse erstellt das MarbleMazeMain Klassenobjekt, startet das Laden von Ressourcen, wiederholt, um den Timer zu aktualisieren, und ruft die MarbleMazeMain::Render Methode in jedem Frame auf. Die Methoden App::OnWindowSizeChanged, App::OnDpiChangedund App::OnOrientationChanged rufen jeweils die Methode MarbleMazeMain::CreateWindowSizeDependentResources auf, und die Methode App::Run ruft die Methoden MarbleMazeMain::Update und MarbleMazeMain::Render auf.

Das folgende Beispiel zeigt, wo die App::SetWindow-Methode das MarbleMazeMain-Klassenobjekt erstellt. Die DeviceResources-Klasse wird an die Methode übergeben, sodass sie die Direct3D-Objekte zum Rendern verwenden kann.

    m_main = std::unique_ptr<MarbleMazeMain>(new MarbleMazeMain(m_deviceResources));

Die App Klasse startet auch das Laden der verzögerten Ressourcen für das Spiel. Weitere Details finden Sie im nächsten Abschnitt.

Darüber hinaus richtet die App--Klasse die Ereignishandler für die CoreWindow--Ereignisse ein. Wenn die Handler für diese Ereignisse aufgerufen werden, übergeben sie die Eingabe an die MarbleMazeMain Klasse.

Laden von Spielelementen im Hintergrund

Um sicherzustellen, dass Ihr Spiel innerhalb von 5 Sekunden nach dem Start auf Fensterereignisse reagieren kann, empfehlen wir, dass Sie Ihre Spielressourcen asynchron oder im Hintergrund laden. Wenn Ressourcen im Hintergrund geladen werden, kann Ihr Spiel auf Fenster-Events reagieren.

Hinweis

Sie können auch das Hauptmenü anzeigen, wenn es bereit ist, und es den verbleibenden Ressourcen ermöglichen, den Ladevorgang im Hintergrund fortzusetzen. Wenn der Benutzer eine Option aus dem Menü auswählt, bevor alle Ressourcen geladen werden, können Sie angeben, dass Szenenressourcen weiterhin geladen werden, indem Sie beispielsweise eine Statusleiste anzeigen.

 

Selbst wenn Ihr Spiel relativ wenige Spielressourcen enthält, empfiehlt es sich, sie aus zwei Gründen asynchron zu laden. Ein Grund ist, dass es schwierig ist, sicherzustellen, dass alle Ihre Ressourcen schnell auf allen Geräten und allen Konfigurationen geladen werden. Durch die frühzeitige Einbindung des asynchronen Ladens ist Ihr Code darüber hinaus bereit, zu skalieren, während Sie Funktionalität hinzufügen.

Das asynchrone Laden von Ressourcen beginnt mit der App::Load-Methode. Diese Methode verwendet die Task--Klasse, um Spielressourcen im Hintergrund zu laden.

    task<void>([=]()
    {
        m_main->LoadDeferredResources(true, false);
    });

Die MarbleMazeMain Klasse definiert das m_deferredResourcesReady Kennzeichen, um anzugeben, dass das asynchrone Laden abgeschlossen ist. Die MarbleMazeMain::LoadDeferredResources Methode lädt die Spielressourcen und legt dieses Flag fest. Die Update- (MarbleMazeMain::Update) und Render- (MarbleMazeMain::Render) Phasen der App überprüfen dieses Flag. Wenn diese Kennzeichnung festgelegt ist, wird das Spiel normal fortgesetzt. Wenn das Flag noch nicht festgelegt ist, zeigt das Spiel den Ladebildschirm an.

Weitere Informationen zur asynchronen Programmierung für UWP-Apps finden Sie unter Asynchrone Programmierung in C++.

Tipp

Wenn Sie Spielcode schreiben, der Teil einer C++-Bibliothek für Windows-Runtime ist (d. h. eine DLL), überlegen Sie, ob Sie das Erstellen asynchroner Vorgänge in C++ für UWP-Apps lesen möchten, um zu erfahren, wie Sie asynchrone Vorgänge erstellen, die von Apps und anderen Bibliotheken genutzt werden können.

 

Die Spielschleife

Die Methode App::Run führt die Hauptspielschleife aus (MarbleMazeMain::Update). Diese Methode wird für jeden Frame aufgerufen.

Um Ansichts- und Fenstercode vom spielspezifischen Code zu trennen, haben wir die App::Run-Methode implementiert, um Aktualisierungs- und Renderaufrufe an das MarbleMazeMain-Objekt weiterzuleiten.

Das folgende Beispiel zeigt die App::Run-Methode, die die Hauptspielschleife enthält. Die Spielschleife aktualisiert die Gesamtzeit- und Framezeitvariablen, woraufhin sie die Szene aktualisiert und rendert. Dadurch wird auch sichergestellt, dass Inhalte nur gerendert werden, wenn das Fenster sichtbar ist.

void App::Run()
{
    while (!m_windowClosed)
    {
        if (m_windowVisible)
        {
            CoreWindow::GetForCurrentThread()->Dispatcher->
                ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

            m_main->Update();

            if (m_main->Render())
            {
                m_deviceResources->Present();
            }
        }
        else
        {
            CoreWindow::GetForCurrentThread()->Dispatcher->
                ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
        }
    }

    // The app is exiting so do the same thing as if the app were being suspended.
    m_main->OnSuspending();

#ifdef _DEBUG
    // Dump debug info when exiting.
    DumpD3DDebug();
#endif //_DEGBUG
}

Der Zustandsautomat

Spiele enthalten in der Regel eine Zustandsmaschine (auch als endliche Zustandsmaschineoder FSM bezeichnet), um den Fluss und die Reihenfolge der Spiellogik zu steuern. Ein Zustandsautomat enthält eine bestimmte Anzahl von Zuständen und die Fähigkeit, zwischen ihnen zu wechseln. Ein Zustandsautomat beginnt in der Regel bei einem anfänglichen Zustand, wechselt zu einem oder mehreren Zwischenzuständen, und endet möglicherweise in einem Endzustand.

Eine Spielschleife verwendet häufig einen Zustandsautomaten, damit sie die Logik ausführen kann, die für den aktuellen Spielzustand spezifisch ist. Marble Maze definiert die GameState-Aufzählung , die jeden möglichen Zustand des Spiels definiert.

enum class GameState
{
    Initial,
    MainMenu,
    HighScoreDisplay,
    PreGameCountdown,
    InGameActive,
    InGamePaused,
    PostGameResults,
};

Der MainMenu Zustand definiert beispielsweise, dass das Hauptmenü angezeigt wird und das Spiel nicht aktiv ist. Umgekehrt definiert der InGameActive- Zustand, dass das Spiel aktiv ist und dass das Menü nicht angezeigt wird. Die Klasse MarbleMazeMain definiert die Membervariable m_gameState, um den aktiven Spielzustand zu halten.

Die MarbleMazeMain::Update und MarbleMazeMain::Render Methoden verwenden Switch-Anweisungen, um Logik für den aktuellen Zustand auszuführen. Das folgende Beispiel zeigt, wie eine Switch-Anweisung für die MarbleMazeMain::Update-Methode aussehen könnte (Details wurden entfernt, um die Struktur zu veranschaulichen).

switch (m_gameState)
{
case GameState::MainMenu:
    // Do something with the main menu. 
    break;

case GameState::HighScoreDisplay:
    // Do something with the high-score table. 
    break;

case GameState::PostGameResults:
    // Do something with the game results. 
    break;

case GameState::InGamePaused:
    // Handle the paused state. 
    break;
}

Wenn Die Spiellogik oder das Rendering von einem bestimmten Spielzustand abhängig ist, wird sie in dieser Dokumentation hervorgehoben.

Behandeln von App- und Fensterereignissen

Die Windows-Runtime bietet ein objektorientiertes Ereignisbehandlungssystem, sodass Sie Windows-Nachrichten einfacher verwalten können. Um ein Ereignis in einer Anwendung zu nutzen, müssen Sie einen Ereignishandler oder eine Ereignisbehandlungsmethode bereitstellen, die auf das Ereignis reagiert. Auch müssen Sie den Ereignishandler bei der Ereignisquelle registrieren. Dieser Prozess wird häufig als Ereignisverkabelung bezeichnet.

Unterstützen des Anhaltens, Fortsetzens und Neustarts

Marble Maze wird angehalten, wenn der Benutzer wegschaltet oder wenn Windows in den Energiesparmodus wechselt. Das Spiel wird fortgesetzt, wenn der Benutzer es in den Vordergrund verschiebt oder wenn Windows aus einem Energiesparmodus kommt. Im Allgemeinen schließen Sie keine Apps. Windows kann die App beenden, wenn sie sich im angehaltenen Zustand befindet, und Windows benötigt die Ressourcen, die die App verwendet, z. B. Arbeitsspeicher. Windows benachrichtigt eine App, wenn sie angehalten oder fortgesetzt werden soll, aber sie benachrichtigt die App nicht, wenn sie beendet werden soll. Daher muss Ihre App in der Lage sein, zu dem Zeitpunkt, an dem Windows Ihre App benachrichtigt, dass sie ausgesetzt wird, alle Daten zu speichern, die zum Wiederherstellen des aktuellen Benutzerstatus erforderlich wären, wenn die App neu gestartet wird. Wenn Ihre App einen erheblichen Benutzerstatus aufweist, der teuer zu speichern ist, müssen Sie den Zustand möglicherweise auch regelmäßig speichern, bevor Ihre App die Anhaltebenachrichtigung empfängt. Marble Maze reagiert aus zwei Gründen auf Anhalte- und Fortsetzungsbenachrichtigungen:

  1. Wenn die App angehalten wird, speichert das Spiel den aktuellen Spielzustand und hält die Audiowiedergabe an. Wenn die App wiederaufgenommen wird, wird die Audiowiedergabe des Spiels fortgesetzt.
  2. Wenn die App geschlossen und später neu gestartet wird, setzt das Spiel an der vorherigen Stelle fort.

Marble Maze führt die folgenden Aufgaben aus, um das Anhalten und Fortsetzen zu unterstützen:

  • Er speichert seinen Zustand in permanentem Speicher an wichtigen Punkten im Spiel, z. B. wenn der Benutzer einen Prüfpunkt erreicht.
  • Es reagiert auf angehaltene Benachrichtigungen, indem es seinen Zustand zwischenspeichert.
  • Er reagiert auf Wiederaufnahmebenachrichtigungen, indem er seinen Zustand aus dem persistenten Speicher lädt. Außerdem wird der vorherige Zustand beim Start geladen.

Um das Anhalten und Fortsetzen zu unterstützen, definiert Marble Maze die Klasse PersistentState. (Siehe PersistentState.h und PersistentState.cpp). Diese Klasse verwendet die Windows::Foundation::Collections::IPropertySet-Schnittstelle zum Lesen und Schreiben von Eigenschaften. Die PersistentState-Klasse stellt Methoden bereit, die primitive Datentypen lesen und schreiben (z. B. bool, int, float, XMFLOAT3 und Platform::String), von und in einen Sicherungsspeicher.

ref class PersistentState
{
internal:
    void Initialize(
        _In_ Windows::Foundation::Collections::IPropertySet^ settingsValues,
        _In_ Platform::String^ key
        );

    void SaveBool(Platform::String^ key, bool value);
    void SaveInt32(Platform::String^ key, int value);
    void SaveSingle(Platform::String^ key, float value);
    void SaveXMFLOAT3(Platform::String^ key, DirectX::XMFLOAT3 value);
    void SaveString(Platform::String^ key, Platform::String^ string);

    bool LoadBool(Platform::String^ key, bool defaultValue);
    int  LoadInt32(Platform::String^ key, int defaultValue);
    float LoadSingle(Platform::String^ key, float defaultValue);

    DirectX::XMFLOAT3 LoadXMFLOAT3(
        Platform::String^ key, 
        DirectX::XMFLOAT3 defaultValue);

    Platform::String^ LoadString(
        Platform::String^ key, 
        Platform::String^ defaultValue);

private:
    Platform::String^ m_keyName;
    Windows::Foundation::Collections::IPropertySet^ m_settingsValues;
};

Die MarbleMazeMain-Klasse enthält ein PersistentState-Objekt. Der MarbleMazeMain Konstruktor initialisiert dieses Objekt und stellt den lokalen Anwendungsdatenspeicher als Sicherungsdatenspeicher bereit.

m_persistentState = ref new PersistentState();

m_persistentState->Initialize(
    Windows::Storage::ApplicationData::Current->LocalSettings->Values,
    "MarbleMaze");

Marble Maze speichert seinen Zustand, wenn die Murmel einen Prüfpunkt oder das Ziel passiert (in der Methode MarbleMazeMain::Update), und wenn das Fenster den Fokus verliert (in der Methode MarbleMazeMain::OnFocusChange). Wenn Ihr Spiel eine große Menge an Zustandsdaten aufweist, wird empfohlen, gelegentlich den Zustand in ähnlicher Weise im beständigen Speicher zu speichern, da Sie nur einige Sekunden haben, um auf die Pausebenachrichtigung zu reagieren. Wenn Ihre App daher eine Aussetzungsbenachrichtigung empfängt, muss sie nur die Zustandsdaten speichern, die sich geändert haben.

Um auf Anhalte- und Fortsetzungsbenachrichtigungen zu reagieren, definiert die MarbleMazeMain-Klasse die SaveState-- und LoadState--Methoden, die beim Anhalten und Fortsetzen aufgerufen werden. Die MarbleMazeMain::OnSuspending-Methode behandelt das Anhalteereignis und die MarbleMazeMain::OnResuming-Methode behandelt das Fortsetzungsereignis.

Die MarbleMazeMain::OnSuspending-Methode speichert den Spielzustand und unterbricht die Audioausgabe.

void MarbleMazeMain::OnSuspending()
{
    SaveState();
    m_audio.SuspendAudio();
}

Die MarbleMazeMain::SaveState-Methode speichert Spielzustandswerte wie die aktuelle Position und Geschwindigkeit der Murmel, den letzten Prüfpunkt und die Highscoretabelle.

void MarbleMazeMain::SaveState()
{
    m_persistentState->SaveXMFLOAT3(":Position", m_physics.GetPosition());
    m_persistentState->SaveXMFLOAT3(":Velocity", m_physics.GetVelocity());

    m_persistentState->SaveSingle(
        ":ElapsedTime", 
        m_inGameStopwatchTimer.GetElapsedTime());

    m_persistentState->SaveInt32(":GameState", static_cast<int>(m_gameState));
    m_persistentState->SaveInt32(":Checkpoint", static_cast<int>(m_currentCheckpoint));

    int i = 0;
    HighScoreEntries entries = m_highScoreTable.GetEntries();
    const int bufferLength = 16;
    char16 str[bufferLength];

    m_persistentState->SaveInt32(":ScoreCount", static_cast<int>(entries.size()));

    for (auto iter = entries.begin(); iter != entries.end(); ++iter)
    {
        int len = swprintf_s(str, bufferLength, L"%d", i++);
        Platform::String^ string = ref new Platform::String(str, len);

        m_persistentState->SaveSingle(
            Platform::String::Concat(":ScoreTime", string), 
            iter->elapsedTime);

        m_persistentState->SaveString(
            Platform::String::Concat(":ScoreTag", string), 
            iter->tag);
    }
}

Wenn das Spiel fortgesetzt wird, muss nur das Audio weiterlaufen. Der Zustand muss nicht aus dem persistenten Speicher geladen werden, da er bereits im Arbeitsspeicher vorhanden ist.

Im Dokument Hinzufügen von Audio zum Marble Maze-Beispielwird erläutert, wie das Spiel Audio anhält und fortsetzt.

Um den Neustart zu unterstützen, ruft der MarbleMazeMain-Konstruktor , der während des Starts aufgerufen wird, die MarbleMazeMain::LoadState-Methode auf. Die MarbleMazeMain::LoadState Methode liest den Zustand und wendet ihn auf die Spielobjekte an. Diese Methode setzt auch den aktuellen Spielzustand auf pausiert, wenn das Spiel beim Anhalten entweder pausiert oder aktiv war. Wir unterbrechen das Spiel, damit der Benutzer nicht von unerwarteten Aktivitäten überrascht ist. Es wechselt auch zum Hauptmenü, wenn sich das Spiel nicht im Spielmodus befand, als es angehalten wurde.

void MarbleMazeMain::LoadState()
{
    XMFLOAT3 position = m_persistentState->LoadXMFLOAT3(
        ":Position", 
        m_physics.GetPosition());

    XMFLOAT3 velocity = m_persistentState->LoadXMFLOAT3(
        ":Velocity", 
        m_physics.GetVelocity());

    float elapsedTime = m_persistentState->LoadSingle(":ElapsedTime", 0.0f);

    int gameState = m_persistentState->LoadInt32(
        ":GameState", 
        static_cast<int>(m_gameState));

    int currentCheckpoint = m_persistentState->LoadInt32(
        ":Checkpoint", 
        static_cast<int>(m_currentCheckpoint));

    switch (static_cast<GameState>(gameState))
    {
    case GameState::Initial:
        break;

    case GameState::MainMenu:
    case GameState::HighScoreDisplay:
    case GameState::PreGameCountdown:
    case GameState::PostGameResults:
        SetGameState(GameState::MainMenu);
        break;

    case GameState::InGameActive:
    case GameState::InGamePaused:
        m_inGameStopwatchTimer.SetVisible(true);
        m_inGameStopwatchTimer.SetElapsedTime(elapsedTime);
        m_physics.SetPosition(position);
        m_physics.SetVelocity(velocity);
        m_currentCheckpoint = currentCheckpoint;
        SetGameState(GameState::InGamePaused);
        break;
    }

    int count = m_persistentState->LoadInt32(":ScoreCount", 0);

    const int bufferLength = 16;
    char16 str[bufferLength];

    for (int i = 0; i < count; i++)
    {
        HighScoreEntry entry;
        int len = swprintf_s(str, bufferLength, L"%d", i);
        Platform::String^ string = ref new Platform::String(str, len);

        entry.elapsedTime = m_persistentState->LoadSingle(
            Platform::String::Concat(":ScoreTime", string), 
            0.0f);

        entry.tag = m_persistentState->LoadString(
            Platform::String::Concat(":ScoreTag", string), 
            L"");

        m_highScoreTable.AddScoreToTable(entry);
    }
}

Von Bedeutung

Marble Maze unterscheidet nicht zwischen einem Kaltstart – d. h. zum ersten Mal ohne vorheriges Anhalteereignis – und dem Fortsetzen aus einem angehaltenen Zustand. Dies wird für alle UWP-Apps empfohlen.

Weitere Informationen zu Anwendungsdaten finden Sie unter Speichern und Abrufen von Einstellungen und anderen App-Daten.

Nächste Schritte

Lesen Sie Hinzufügen visueller Inhalte zum Marble Maze-Beispiel, um Informationen zu einigen der wichtigsten Methoden zu erhalten, die Sie berücksichtigen sollten, wenn Sie mit visuellen Ressourcen arbeiten.