Definieren des Hauptobjekts für das Spiel
Hinweis
Dieses Thema ist Teil der Erstellen eines einfachen UWP-Spiels (Universelle Windows-Plattform) mit DirectX-Tutorial-Reihe. Das Thema unter diesem Link legt den Kontext für die Reihe fest.
Nachdem Sie das grundlegende Framework des Beispielspiels eingerichtet und einen Zustandsautomat implementiert haben, der das allgemeine Benutzer- und Systemverhalten behandelt, sollten Sie die Regeln und Mechanismen untersuchen, mit denen das Beispielspiel in ein Spiel umgewandelt wird. Sehen wir uns die Details des Hauptobjekts des Beispielspiels an und erfahren, wie Spielregeln in Interaktionen mit der Spielwelt übersetzt werden.
Ziele
- Erfahren Sie, wie Sie grundlegende Entwicklungstechniken anwenden, um Spielregeln und -mechanismen für ein UWP-DirectX-Spiel zu implementieren.
Hauptobjekt für das Spiel
Im Simple3DGameDX-Beispielspiel ist Simple3DGame die Hauptobjektklasse des Spiels. Eine Instanz von Simple3DGame wird indirekt über die App::Load-Methode erstellt.
Hier sind einige der Features der Simple3DGame-Klasse .
- Enthält die Implementierung der Spiellogik.
- Enthält Methoden, die diese Details kommunizieren.
- Änderungen im Spielzustand an den Zustandsautomat, der im App-Framework definiert ist.
- Änderungen im Spielzustand von der App in das Spielobjekt selbst.
- Details zum Aktualisieren der Benutzeroberfläche des Spiels (Overlay- und Heads-up-Display), Animationen und Physik (dynamik).
Hinweis
Das Aktualisieren von Grafiken wird von der GameRenderer-Klasse behandelt, die Methoden zum Abrufen und Verwenden von Grafikgeräteressourcen enthält, die vom Spiel verwendet werden. Weitere Informationen finden Sie unter Renderingframework I: Einführung in das Rendering.
- Dient als Container für die Daten, die eine Spielsitzung, ein Level oder eine Lebensdauer definieren, je nachdem, wie Sie Ihr Spiel auf hoher Ebene definieren. In diesem Fall werden die Spielzustandsdaten für die Lebensdauer des Spiels verwendet und einmal initialisiert, wenn ein Benutzer das Spiel startet.
Informationen zum Anzeigen der von dieser Klasse definierten Methoden und Daten finden Sie unter Der Simple3DGame-Klasse unten.
Initialisieren und Starten des Spiels
Wenn ein Spieler das Spiel startet, muss das Spielobjekt seinen Zustand initialisieren, das Overlay erstellen und hinzufügen, die Variablen festlegen, die die Leistung des Spielers verfolgen, und die Objekte instanziieren, die zum Erstellen der Levels verwendet werden. In diesem Beispiel erfolgt dies, wenn die GameMain-Instanz in "App::Load" erstellt wird.
Das Spielobjekt vom Typ Simple3DGame wird im GameMain::GameMain-Konstruktor erstellt. Anschließend wird sie mithilfe der Simple3DGame::Initialize-Methode während der GameMain::ConstructInBackground fire-and-forget coroutine initialisiert, die von GameMain::GameMain aufgerufen wird.
Die Simple3DGame::Initialize-Methode
Das Beispielspiel richtet diese Komponenten im Spielobjekt ein.
- Es wird ein neues Audiowiedergabeobjekt erstellt.
- Arrays für die Grafikgrundtypen des Spiels werden erstellt, einschließlich Arrays für die Levelgrundtypen, Munition und Hindernisse.
- Ein Speicherort zum Speichern von Spielzustandsdaten wird erstellt, mit dem Namen "Spiel" und in dem durch ApplicationData::Current angegebenen Speicherort der App-Dateneinstellungen platziert.
- Ein Spielzeitgeber und die anfängliche In-Game-Overlaybitmap werden erstellt.
- Eine neue Kamera wird mit einem bestimmten Satz von Ansichts- und Projektionsparametern erstellt.
- Das Eingabegerät (der Controller) wird auf den gleichen Anfangswinkel und Gierwinkel wie die Kamera festgelegt, sodass der Spieler eine 1:1-Entsprechung zwischen der Startsteuerungsposition und der Kameraposition aufweist.
- Das Spielerobjekt wird erstellt und auf "aktiv" festgelegt. Wir verwenden ein Kugelobjekt, um die Nähe des Spielers zu Wänden und Hindernissen zu erkennen und die Kamera daran zu hindern, in einer Position platziert zu werden, die das Eintauchen unterbricht.
- Der Grundtyp der Spielwelt wird erstellt.
- Die Zylinderhindernisse werden geschaffen.
- Die Ziele (Face-Objekte ) werden erstellt und nummeriert.
- Die Munitionskugeln werden geschaffen.
- Die Ebenen werden erstellt.
- Der Highscore wird geladen.
- Alle vorherigen gespeicherten Spielzustände werden geladen.
Das Spiel verfügt jetzt über Instanzen aller wichtigen Komponenten – die Welt, der Spieler, die Hindernisse, die Ziele und die Munitionskugeln. Außerdem gibt es Instanzen der Ebenen, die Konfigurationen aller oben genannten Komponenten und deren Verhalten für jede bestimmte Ebene darstellen. Sehen wir uns nun an, wie das Spiel die Levels erstellt.
Erstellen und Laden von Spielebenen
Der großteil der schweren Hebearbeiten für den Levelaufbau erfolgt in den Level[N].h/.cpp
Dateien im Ordner "GameLevels " der Beispiellösung. Da sie sich auf eine sehr spezifische Implementierung konzentriert, werden wir sie hier nicht behandeln. Wichtig ist, dass der Code für jede Ebene als separates Level[N] -Objekt ausgeführt wird. Wenn Sie das Spiel erweitern möchten, können Sie ein Level[N] -Objekt erstellen, das eine zugewiesene Zahl als Parameter akzeptiert und die Hindernisse und Ziele zufällig platziert. Sie können auch Konfigurationsdaten der Ebene aus einer Ressourcendatei oder sogar aus dem Internet laden.
Definieren des Spiels
An diesem Punkt haben wir alle Komponenten, die wir zum Entwickeln des Spiels benötigen. Die Levels wurden aus den Grundtypen im Arbeitsspeicher erstellt und sind bereit, damit der Spieler mit der Interaktion beginnen kann.
Die besten Spiele reagieren sofort auf Spielereingaben und geben sofortiges Feedback. Dies gilt für jede Art eines Spiels, von Twitch-Action, Echtzeit-First-Person-Shootern bis hin zu durchdachten, rundenbasierten Strategiespielen.
Die Simple3DGame::RunGame-Methode
Während ein Spiellevel ausgeführt wird, befindet sich das Spiel im Dynamics-Zustand .
GameMain::Update ist die Hauptaktualisierungsschleife, die den Anwendungsstatus einmal pro Frame aktualisiert, wie unten dargestellt. Die Updateschleife ruft die Simple3DGame::RunGame-Methode auf, um die Arbeit zu verarbeiten, wenn sich das Spiel im Dynamics-Zustand befindet.
// Updates the application state once per frame.
void GameMain::Update()
{
// The controller object has its own update loop.
m_controller->Update();
switch (m_updateState)
{
...
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)
{
...
Simple3DGame::RunGame behandelt den Satz von Daten, der den aktuellen Zustand des Spielspiels für die aktuelle Iteration der Spielschleife definiert.
Hier ist die Spielflusslogik in Simple3DGame::RunGame.
- Die Methode aktualisiert den Timer, der die Sekunden nach unten zählt, bis die Ebene abgeschlossen ist, und überprüft, ob die Zeit der Ebene abgelaufen ist. Dies ist eine der Regeln des Spiels – wenn die Zeit abgelaufen ist, wenn nicht alle Ziele gedreht wurden, dann ist es spielüber.
- Wenn die Zeit abgelaufen ist, legt die Methode den TimeExpired-Spielzustand fest und kehrt zur Update-Methode im vorherigen Code zurück.
- Wenn die Zeit verbleibt, wird der Bewegungs-Blick-Controller für eine Aktualisierung der Kameraposition abgefragt; Insbesondere eine Aktualisierung des Winkels der Ansicht normal projiziert von der Kameraebene (wo der Spieler sucht), und der Abstand, den der Winkel seit dem letzten Abruf des Controllers bewegt hat.
- Die Kamera wird basierend auf den neuen Daten des Bewegungs-Blickcontrollers aktualisiert.
- Die Dynamik oder die Animationen und Verhaltensweisen von Objekten in der Spielwelt unabhängig von der Spielersteuerung werden aktualisiert. In diesem Beispielspiel wird die Simple3DGame::UpdateDynamics-Methode aufgerufen, um die Bewegung der abgefeuerten Munitionskugeln, die Animation der Säulenhindernisse und die Bewegung der Ziele zu aktualisieren. Weitere Informationen finden Sie unter "Aktualisieren der Spielwelt".
- Die Methode überprüft, ob die Kriterien für den erfolgreichen Abschluss einer Stufe erfüllt wurden. Wenn dies der Fall ist, wird die Bewertung für das Level abgeschlossen und überprüft, ob es sich um die letzte Stufe (von 6) handelt. Wenn es sich um die letzte Ebene handelt, gibt die Methode den GameState::GameComplete-Spielzustand zurück. Andernfalls wird der GameState::LevelComplete-Spielzustand zurückgegeben.
- Wenn der Level nicht abgeschlossen ist, legt die Methode den Spielzustand auf GameState::Active fest und gibt zurück.
Aktualisieren der Spielwelt
In diesem Beispiel wird beim Ausführen des Spiels die Simple3DGame::UpdateDynamics-Methode aus der Simple3DGame::RunGame-Methode (die von GameMain::Update aufgerufen wird) aufgerufen, um Objekte zu aktualisieren, die in einer Spielszene gerendert werden.
Eine Schleife wie UpdateDynamics ruft alle Methoden auf, die verwendet werden, um die Spielwelt in Bewegung zu setzen, unabhängig von der Spielereingabe, um ein immersives Spielerlebnis zu erstellen und das Level lebendig zu machen. Dazu gehören Grafiken, die gerendert werden müssen, und das Ausführen von Animationsschleifen, um eine dynamische Welt zu erzielen, auch wenn keine Spielereingabe vorhanden ist. In Ihrem Spiel könnte das Bäume, die in winden, Wellen entlang der Uferlinien, Maschinenrauchen und außerirdischen Monstern streifen und bewegen. Es umfasst auch die Interaktion zwischen Objekten, einschließlich Kollisionen zwischen der Spielerkugel und der Welt, oder zwischen der Munition und den Hindernissen und Zielen.
Außer wenn das Spiel speziell angehalten wird, sollte die Spielschleife die Spielwelt weiterhin aktualisieren. ob dies auf Spiellogik, physischen Algorithmen oder auf einfachem Zufallsprinzip basiert.
Im Beispielspiel wird dieses Prinzip als Dynamik bezeichnet und umfasst den Aufstieg und Fall der Säulenhindernisse sowie die Bewegung und das physische Verhalten der Munitionskugeln, während sie ausgelöst und in Bewegung gesetzt werden.
Die Simple3DGame::UpdateDynamics-Methode
Diese Methode befasst sich mit diesen vier Berechnungssätzen.
- Die Positionen der abgefeuerten Munitionskugeln in der Welt.
- Die Animation der Säulenhindernisse.
- Die Schnittmenge des Spielers und der Weltgrenzen.
- Die Kollisionen der Munitionskugeln mit den Hindernissen, den Zielen, anderen Munitionskugeln und der Welt.
Die Animation der Hindernisse erfolgt in einer Schleife, die in den Quellcodedateien Animate.h/.cpp definiert ist. Das Verhalten der Munition und alle Kollisionen werden durch vereinfachte Physikalgorithmen definiert, die im Code bereitgestellt und durch eine Reihe globaler Konstanten für die Spielwelt parametrisiert werden, einschließlich Schwerkraft- und Materialeigenschaften. Dies wird alle im Koordinatenbereich der Spielwelt berechnet.
Überprüfen des Flusses
Nachdem wir nun alle Objekte in der Szene aktualisiert und alle Kollisionen berechnet haben, müssen wir diese Informationen verwenden, um die entsprechenden visuellen Änderungen zu zeichnen.
Nachdem GameMain::Update die aktuelle Iteration der Spielschleife abgeschlossen hat, ruft das Beispiel sofort GameRenderer::Render auf, um die aktualisierten Objektdaten zu übernehmen und eine neue Szene zu generieren, die dem Spieler präsentiert wird, wie unten dargestellt.
void GameMain::Run()
{
while (!m_windowClosed)
{
if (m_visible)
{
switch (m_updateState)
{
case UpdateEngineState::Deactivated:
case UpdateEngineState::TooSmall:
...
// Otherwise, fall through and do normal processing to perform rendering.
default:
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(
CoreProcessEventsOption::ProcessAllIfPresent);
// GameMain::Update calls Simple3DGame::RunGame. If game is in Dynamics
// state, uses Simple3DGame::UpdateDynamics to update game world.
Update();
// Render is called immediately after the Update loop.
m_renderer->Render();
m_deviceResources->Present();
m_renderNeeded = false;
}
}
else
{
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(
CoreProcessEventsOption::ProcessOneAndAllPending);
}
}
m_game->OnSuspending(); // Exiting due to window close, so save state.
}
Rendern der Grafik der Spielwelt
Es wird empfohlen, dass die Grafiken in einem Spiel häufig aktualisiert werden, idealerweise genau so oft wie die Hauptspielschleife durchlaufen wird. Während die Schleife durchlaufen wird, wird der Zustand der Spielwelt mit oder ohne Spielereingabe aktualisiert. Dadurch können berechnete Animationen und Verhaltensweisen reibungslos angezeigt werden. Stellen Sie sich vor, wenn wir eine einfache Szene mit Wasser hatten, die nur bewegt wurde, wenn der Spieler eine Taste gedrückt hat. Das wäre nicht realistisch; Ein gutes Spiel sieht reibungslos und flüssig aus.
Erinnern Sie sich an die Schleife des Beispielspiels wie oben in GameMain::Run gezeigt. Wenn das Hauptfenster des Spiels sichtbar ist und nicht angedockt oder deaktiviert ist, wird das Spiel weiterhin aktualisiert und die Ergebnisse dieses Updates gerendert. Die GameRenderer::Render-Methode , die wir als Nächstes untersuchen, rendert eine Darstellung dieses Zustands. Dies erfolgt unmittelbar nach einem Aufruf von GameMain::Update, das Simple3DGame::RunGame zum Aktualisieren der Zustände enthält, wie im vorherigen Abschnitt beschrieben.
GameRenderer::Render zeichnet die Projektion der 3D-Welt und zeichnet dann das Direct2D-Overlay darüber. Nach Abschluss wird die endgültige Swapchain mit den kombinierten Puffern für die Anzeige angezeigt.
Hinweis
Es gibt zwei Zustände für das Direct2D-Overlay des Beispielspiels– eines, in dem das Spiel das Spielinfo-Overlay anzeigt, das die Bitmap für das Pausenmenü enthält, und eines, in dem das Spiel die Fadenkreuze zusammen mit den Rechtecke für den Touchscreen-Bewegungs-Blickcontroller anzeigt. Der Scoretext wird in beiden Zuständen gezeichnet. Weitere Informationen finden Sie unter Renderingframework I: Einführung in Rendering.
Die GameRenderer::Render-Methode
void GameRenderer::Render()
{
bool stereoEnabled{ m_deviceResources->GetStereoState() };
auto d3dContext{ m_deviceResources->GetD3DDeviceContext() };
auto d2dContext{ m_deviceResources->GetD2DDeviceContext() };
...
if (m_game != nullptr && m_gameResourcesLoaded && m_levelResourcesLoaded)
{
// This section is only used after the game state has been initialized and all device
// resources needed for the game have been created and associated with the game objects.
...
for (auto&& object : m_game->RenderObjects())
{
object->Render(d3dContext, m_constantBufferChangesEveryPrim.get());
}
}
d3dContext->BeginEventInt(L"D2D BeginDraw", 1);
d2dContext->BeginDraw();
// To handle the swapchain being pre-rotated, set the D2D transformation to include it.
d2dContext->SetTransform(m_deviceResources->GetOrientationTransform2D());
if (m_game != nullptr && m_gameResourcesLoaded)
{
// This is only used after the game state has been initialized.
m_gameHud.Render(m_game);
}
if (m_gameInfoOverlay.Visible())
{
d2dContext->DrawBitmap(
m_gameInfoOverlay.Bitmap(),
m_gameInfoOverlayRect
);
}
...
}
}
Die Simple3DGame-Klasse
Dies sind die Methoden und Datenmmber, die von der Simple3DGame-Klasse definiert werden.
Memberfunktionen
Zu den von Simple3DGame definierten öffentlichen Memberfunktionen gehören die folgenden Funktionen.
- Initialisieren. Legt die Startwerte der globalen Variablen fest und initialisiert die Spielobjekte. Dies wird im Abschnitt "Initialisieren" behandelt und der Spielabschnitt gestartet.
- LoadGame. Initialisiert eine neue Ebene und startet das Laden.
- LoadLevelAsync. Eine Coroutine, die die Ebene initialisiert, und ruft dann eine weitere Coroutine auf dem Renderer auf, um die gerätespezifischen Ebenenressourcen zu laden. Diese Methode wird in einem separaten Thread ausgeführt; Daher können nur ID3D11Device-Methoden (im Gegensatz zu ID3D11DeviceContext-Methoden ) aus diesem Thread aufgerufen werden. Alle Gerätekontextmethoden werden in der FinalizeLoadLevel-Methode aufgerufen. Wenn Sie noch nicht mit der asynchronen Programmierung vertraut sind, lesen Sie Parallelität und asynchrone Vorgänge mit C++/WinRT.
- FinalizeLoadLevel. Schließt alle Schritte zum Laden der Ebene ab, die im Hauptthread ausgeführt werden muss. Dazu gehören alle Aufrufe von Direct3D 11-Gerätekontextmethoden (ID3D11DeviceContext).
- StartLevel. Startet das Spiel für ein neues Level.
- PauseGame. Hält das Spiel an.
- RunGame. Führt eine Iteration der Spielschleife aus. Sie wird von "App::Update" einmal aufgerufen, wenn der Spielzustand aktiv ist, bei jeder Iteration der Spielschleife.
- OnSuspending und OnResuming. Anhalten/Fortsetzen der Audiodaten des Spiels.
Hier sind die privaten Memberfunktionen.
- LoadSavedState und SaveState. Laden/Speichern Sie den aktuellen Zustand des Spiels bzw. den aktuellen Zustand des Spiels.
- LoadHighScore und SaveHighScore. Laden/speichern Sie den Highscore für spieleübergreifend.
- InitializeAmmo. Setzt den Zustand jedes Kugelobjekts zurück, das als Munition für den Anfang jeder Runde verwendet wird.
- UpdateDynamics. Dies ist eine wichtige Methode, da alle Spielobjekte basierend auf canned-Animationsroutinen, Physik und Steuerungseingabe aktualisiert werden. Dies ist das Herzstück der Interaktivität, die das Spiel definiert. Dies wird im Abschnitt "Spielwelt aktualisieren " behandelt.
Die anderen öffentlichen Methoden sind Eigenschaftenaccessor, die Spiel- und Overlay-spezifische Informationen an das App-Framework für die Anzeige zurückgeben.
Datenmember
Diese Objekte werden aktualisiert, wenn die Spielschleife ausgeführt wird.
- MoveLookController-Objekt . Stellt die Spielereingabe dar. Weitere Informationen finden Sie unter Hinzufügen von Steuerelementen.
- GameRenderer-Objekt . Stellt einen Direct3D 11-Renderer dar, der alle gerätespezifischen Objekte und deren Rendering verarbeitet. Weitere Informationen finden Sie unter Renderingframework I.
- Audioobjekt . Steuert die Audiowiedergabe für das Spiel. Weitere Informationen finden Sie unter Hinzufügen von Sound.
Die restlichen Spielvariablen enthalten die Listen der Grundtypen sowie ihre jeweiligen Spielmengen sowie spielspezifische Daten und Einschränkungen.
Nächste Schritte
Wir müssen noch über das eigentliche Renderingmodul sprechen – wie Aufrufe der Rendermethoden für die aktualisierten Grundtypen in Pixel auf dem Bildschirm umgewandelt werden. Diese Aspekte werden in zwei Teilen behandelt: Renderingframework I: Einführung in das Rendering - und Renderingframework II: Spielrendering. Wenn Sie mehr daran interessiert sind, wie die Spielersteuerelemente den Spielzustand aktualisieren, lesen Sie das Hinzufügen von Steuerelementen.