Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Strukturen för en DirectX UWP-app (Universal Windows Platform) skiljer sig från den för ett traditionellt skrivbordsprogram. I stället för att arbeta med referenstyper som HWND och funktioner som CreateWindow tillhandahåller Windows Runtime gränssnitt som Windows::UI::Core::ICoreWindow så att du kan utveckla UWP-appar på ett modernare, objektorienterat sätt. Det här avsnittet i dokumentationen visar hur marble maze-appkoden är strukturerad.
Anmärkning
Exempelkoden som motsvarar det här dokumentet finns i DirectX Marble Maze-spelexemplet.
Här är några av de viktigaste punkterna som beskrivs i det här dokumentet när du strukturerar din spelkod:
- I initieringsfasen konfigurerar du de körnings- och bibliotekskomponenter som ditt spel använder och läser in spelspecifika resurser.
- UWP-appar måste börja bearbeta händelser inom 5 sekunder efter starten. Läs därför bara in viktiga resurser när du läser in din app. Spel bör läsa in stora resurser i bakgrunden och visa en förloppsindikator.
- I spelloopen svarar du på Windows-händelser, läser in användarindata, uppdaterar scenobjekt och renderar scenen.
- Använd händelsehanterare för att svara på fönsterhändelser. (Dessa ersätter fönstermeddelandena från Windows-skrivbordsprogram.)
- Använd en tillståndsdator för att styra flödet och ordningen på spellogik.
Filorganisation
Några av komponenterna i Marble Maze kan återanvändas med valfritt spel med liten eller ingen ändring. För ditt eget spel kan du anpassa organisationen och idéerna som dessa filer tillhandahåller. I följande tabell beskrivs kortfattat de viktiga källkodsfilerna.
| Filer | Beskrivning |
|---|---|
| App.h, App.cpp | Definierar app- och DirectXApplicationSource-klasserna , som kapslar in vyn (fönster, tråd och händelser) för appen. |
| Audio.h, Audio.cpp | Definierar klassen Ljud , som hanterar ljudresurser. |
| BasicLoader.h, BasicLoader.cpp | Definierar klassen BasicLoader , som innehåller verktygsmetoder som hjälper dig att läsa in texturer, nät och skuggningar. |
| BasicMath.h | Definierar strukturer och funktioner som hjälper dig att arbeta med vektor- och matrisdata och beräkningar. Många av dessa funktioner är kompatibla med HLSL-skuggningstyper. |
| BasicReaderWriter.h, BasicReaderWriter.cpp | Definierar klassen BasicReaderWriter , som använder Windows Runtime för att läsa och skriva fildata i en UWP-app. |
| BasicShapes.h, BasicShapes.cpp | Definierar klassen BasicShapes , som tillhandahåller verktygsmetoder för att skapa grundläggande former som kuber och sfärer. (Dessa filer används inte av Marble Maze-implementeringen). |
| Camera.h, Camera.cpp | Definierar klassen Kamera , som tillhandahåller en kameras position och orientering. |
| Kollision.h, Kollision.cpp | Hanterar kollisionsinformation mellan marmorn och andra objekt, till exempel labyrinten. |
| DDSTextureLoader.h, DDSTextureLoader.cpp | Definierar funktionen CreateDDSTextureFromMemory , som läser in texturer som är i .dds format från en minnesbuffert. |
| DirectXHelper.h | Definierar DirectX-hjälpfunktioner som är användbara för många DirectX UWP-appar. |
| LoadScreen.h, LoadScreen.cpp | Definierar klassen LoadScreen , som visar en inläsningsskärm under appinitieringen. |
| MarbleMazeMain.h, MarbleMazeMain.cpp | Definierar klassen MarbleMazeMain , som hanterar spelspecifika resurser och definierar mycket av spellogik. |
| MediaStreamer.h, MediaStreamer.cpp | Definierar klassen MediaStreamer , som använder Media Foundation för att hjälpa spelet att hantera ljudresurser. |
| PersistentState.h, PersistentState.cpp | Definierar klassen PersistentState , som läser och skriver primitiva datatyper från och till ett lagringslager. |
| Fysik.h, Fysik.cpp | Definierar fysikklassen , som implementerar fysiksimuleringen mellan marmorn och labyrinten. |
| Primitives.h | Definierar geometriska typer som används av spelet. |
| SampleOverlay.h, SampleOverlay.cpp | Definierar klassen SampleOverlay , som tillhandahåller vanliga 2D- och användargränssnittsdata och åtgärder. |
| SDKMesh.h, SDKMesh.cpp | Definierar klassen SDKMesh , som läser in och återger nät som är i SDK Mesh-format (.sdkmesh). |
| StepTimer.h | Definierar klassen StepTimer , vilket är ett enkelt sätt att få total och förfluten tid. |
| UserInterface.h, UserInterface.cpp | Definierar funktioner som är relaterade till användargränssnittet, till exempel menysystemet och tabellen med höga poäng. |
Design-time jämfört med körtidsresursformat
När du kan, använd körtidsformat i stället för designtidsformat för att mer effektivt ladda spelresurser.
Ett design-time-format är det format du använder när du utformar din resurs. Vanligtvis arbetar 3D-designers med designtidsformat. Vissa format för designtid är också textbaserade så att du kan ändra dem i valfri textbaserad redigerare. Designtidsformat kan vara utförliga och innehålla mer information än vad ditt spel kräver. Ett körningsformat är det binära format som läses av ditt spel. Körningstidformat är vanligtvis mer kompakta och effektivare att läsa in än de motsvarande format för designtid. Det är därför de flesta spel använder körningstillgångar i realtid.
Även om ditt spel kan läsa ett designformat direkt finns det flera fördelar med att använda ett separat körtidsformat. Eftersom körningsformat ofta är mer kompakta kräver de mindre diskutrymme och kräver mindre tid att överföra över ett nätverk. Dessutom representeras körningsformat ofta som minnesmappade datastrukturer. Därför kan de läsas in i minnet mycket snabbare än till exempel en XML-baserad textfil. Slutligen, eftersom separata körningsformat vanligtvis är binärkodade, är de svårare för slutanvändaren att ändra.
HLSL-shaders är ett exempel på resurser som använder olika format för designtid och körtid. Marble Maze använder .hlsl som format under designfasen och .cso som format vid körning. En .hlsl-fil innehåller källkod för skuggningen. en .cso-fil innehåller motsvarande skuggningsbytekod. När du konverterar .hlsl-filer offline och tillhandahåller .cso-filer med ditt spel undviker du behovet av att konvertera HLSL-källfiler till bytekod när spelet läses in.
Av instruktionsskäl innehåller Marble Maze-projektet både designtidsformatet och körningsformatet för många resurser, men du behöver bara behålla designtidsformaten i källprojektet för ditt eget spel eftersom du kan konvertera dem till körningsformat när du behöver dem. Den här dokumentationen visar hur du konverterar formaten för designtid till körningsformaten.
Programmets livscykel
Marble Maze följer livscykeln för en typisk UWP-app. Mer information om livscykeln för en UWP-app finns i Applivscykel.
När ett UWP-spel initieras initieras vanligtvis körningskomponenter som Direct3D, Direct2D och eventuella indata-, ljud- eller fysikbibliotek som används. Den läser också in spelspecifika resurser som krävs innan spelet börjar. Den här initieringen sker en gång under en spelsession.
Efter initieringen kör spel vanligtvis spelslingan. I den här loopen utför spel vanligtvis fyra åtgärder: bearbeta Windows-händelser, samla in indata, uppdatera scenobjekt och återge scenen. När spelet uppdaterar scenen kan det tillämpa det aktuella indatatillståndet på scenobjekten och simulera fysiska händelser, till exempel objektkollisioner. Spelet kan också utföra andra aktiviteter som att spela upp ljudeffekter eller skicka data via nätverket. När spelet renderar scenen avbildas scenens aktuella tillstånd och ritas ut på visningsenheten. I följande avsnitt beskrivs dessa aktiviteter mer detaljerat.
Lägga till i mallen
DirectX 11-appmallen (Universal Windows) skapar ett kärnfönster som du kan återge med Direct3D. Mallen innehåller även klassen DeviceResources som skapar alla Direct3D-enhetsresurser som behövs för att återge 3D-innehåll i en UWP-app.
Klassen App skapar klassobjektet MarbleMazeMain, startar inläsningen av resurser, loopar för att uppdatera timern och anropar metoden MarbleMazeMain::Render varje bildruta. App::OnWindowSizeChanged, App::OnDpiChangedoch App::OnOrientationChanged metoderna anropar varje MarbleMazeMain::CreateWindowSizeDependentResources-metoden, och App::Run-metoden anropar MarbleMazeMain::Update och MarbleMazeMain::Render.
I följande exempel visas var metoden App::SetWindow skapar klassobjektet MarbleMazeMain . Klassen DeviceResources skickas till metoden så att den kan använda Direct3D-objekt för återgivning.
m_main = std::unique_ptr<MarbleMazeMain>(new MarbleMazeMain(m_deviceResources));
Klassen App börjar också läsa in de uppskjutna resurserna för spelet. Mer information finns i nästa avsnitt.
Dessutom konfigurerar appklassen händelsehanterare för CoreWindow-händelserna . När hanterare för dessa händelser anropas skickar de indata till klassen MarbleMazeMain .
Laddar spelresurser i bakgrunden
För att säkerställa att ditt spel kan svara på fönsterhändelser inom 5 sekunder efter att det har startats rekommenderar vi att du läser in dina speltillgångar asynkront eller i bakgrunden. När resurser läses in i bakgrunden kan ditt spel svara på fönsterhändelser.
Anmärkning
Du kan också visa huvudmenyn när den är klar samt låta resten av resurserna fortsätta att läsas in i bakgrunden. Om användaren väljer ett alternativ på menyn innan alla resurser läses in kan du ange att scenresurser fortsätter att läsas in genom att till exempel visa ett förloppsfält.
Även om ditt spel innehåller relativt få speltillgångar är det bra att läsa in dem asynkront av två skäl. En orsak är att det är svårt att garantera att alla dina resurser kommer att läsas in snabbt på alla enheter och alla konfigurationer. Genom att införliva asynkron inläsning tidigt är koden också redo att skalas när du lägger till funktioner.
Asynkron inläsning av tillgångar börjar med metoden App::Load. Den här metoden använder uppgift-klassen för att läsa in spelresurser i bakgrunden.
task<void>([=]()
{
m_main->LoadDeferredResources(true, false);
});
Klassen MarbleMazeMain definierar flaggan m_deferredResourcesReady för att indikera att asynkron inläsning är klar. Metoden MarbleMazeMain::LoadDeferredResources läser in spelresurserna och anger sedan den här flaggan. Uppdateringen (MarbleMazeMain::Update) och renderingen (MarbleMazeMain::Render) faserna i appen kontrollerar den här flaggan. När den här flaggan har angetts fortsätter spelet som vanligt. Om flaggan inte har angetts ännu visar spelet laddningsskärmen.
Mer information om asynkron programmering för UWP-appar finns i Asynkron programmering i C++.
Tips/Råd
Om du skriver spelkod som ingår i ett Windows Runtime C++-bibliotek (med andra ord en DLL) bör du överväga om du vill läsa Skapa asynkrona åtgärder i C++ för UWP-appar för att lära dig hur du skapar asynkrona åtgärder som kan användas av appar och andra bibliotek.
Spelloopen
Metoden App::Run kör huvudspelsslingan (MarbleMazeMain::Update). Den här metoden kallas för varje bildruta.
För att separera vy- och fönsterkod från spelspecifik kod implementerade vi metoden App::Run för att vidarebefordra uppdaterings- och återgivningsanrop till MarbleMazeMain-objektet .
I följande exempel visas metoden App::Run , som innehåller huvudspelsloopen. Spelloopen uppdaterar de totala tids- och bildtidsvariablerna och uppdaterar och återger sedan scenen. Detta säkerställer också att innehållet bara återges när fönstret är synligt.
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
}
Tillståndsdatorn
Spel innehåller vanligtvis en tillståndsdator (kallas även för en ändlig tillståndsdator eller FSM) för att styra flödet och ordningen på spellogik. En tillståndsdator innehåller ett visst antal tillstånd och möjligheten att övergå mellan dem. En tillståndsmaskin startar vanligtvis från ett inledande tillstånd, övergår till ett eller flera mellanliggande tillstånd och kan sluta i ett terminalt tillstånd.
En spelloop använder ofta en tillståndsdator så att den kan utföra den logik som är specifik för det aktuella speltillståndet. Marble Maze definierar GameState uppräkning, som definierar varje möjligt tillstånd i spelet.
enum class GameState
{
Initial,
MainMenu,
HighScoreDisplay,
PreGameCountdown,
InGameActive,
InGamePaused,
PostGameResults,
};
MainMenu-tillståndet definierar till exempel att huvudmenyn visas och att spelet inte är aktivt. Omvänt definierar Tillståndet InGameActive att spelet är aktivt och att menyn inte visas. Klassen MarbleMazeMain definierar m_gameState medlemsvariabeln för att hålla det aktiva speltillståndet.
Metoderna MarbleMazeMain::Update och MarbleMazeMain::Render använder switch-instruktioner för att utföra logik för det aktuella tillståndet. I följande exempel visas hur en switch-instruktion kan se ut för metoden MarbleMazeMain::Update (information tas bort för att illustrera strukturen).
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;
}
När spellogik eller återgivning är beroende av ett specifikt speltillstånd betonar vi det i den här dokumentationen.
Hantera app- och fönsterhändelser
Windows Runtime tillhandahåller ett objektorienterat händelsehanteringssystem så att du enklare kan hantera Windows-meddelanden. Om du vill använda en händelse i ett program måste du ange en händelsehanterare eller händelsehanteringsmetod som svarar på händelsen. Du måste också registrera händelsehanteraren med händelsekällan. Den här processen kallas ofta händelsekoppling.
Stöd för att pausa, återuppta och starta om
Marble Maze pausas när användaren växlar bort från den eller när Windows har låg effekt. Spelet återupptas när användaren flyttar det till förgrunden eller när Windows återgår från ett strömsparläge. I allmänhet stänger du inte appar. Windows kan avsluta appen när den är i pausat tillstånd och Windows kräver de resurser, till exempel minne, som appen använder. Windows meddelar en app när den är på väg att pausas eller återupptas, men den meddelar inte appen när den är på väg att avslutas. Därför måste din app kunna spara – när Windows meddelar din app att den är på väg att pausas – alla data som krävs för att återställa det aktuella användartillståndet när appen startas om. Om din app har ett betydande användartillstånd som det är kostsamt att spara, kan du också behöva spara tillstånd regelbundet, även innan appen får ett meddelande om paus. Marble Maze svarar på pausa och återuppta meddelanden av två skäl:
- När appen är avstängd sparar spelet det aktuella speltillståndet och pausar ljuduppspelningen. När appen återupptas återupptas ljuduppspelningen.
- När appen stängs och senare startas om återupptas spelet från sitt tidigare tillstånd.
Marble Maze utför följande uppgifter för att stödja paus och återuppta:
- Det sparar sitt tillstånd till beständig lagring vid viktiga punkter i spelet, till exempel när användaren når en kontrollpunkt.
- Den svarar på suspenderingsmeddelanden genom att spara sin status till ihållande lagring.
- Den svarar på notifieringar om återupptagning genom att läsa in sitt tillstånd från permanent lagring. Den läser också in det tidigare tillståndet under starten.
För att stödja paus och återuppta definierar Marble Maze klassen PersistentState. (Se PersistentState.h och PersistentState.cpp). Den här klassen använder gränssnittet Windows::Foundation::Collections::IPropertySet för att läsa och skriva egenskaper. Klassen PersistentState innehåller metoder som läser och skriver primitiva datatyper (till exempel bool, int, float, XMFLOAT3 och Platform::String), från och till ett lagringsplats för stöd.
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;
};
Klassen MarbleMazeMain innehåller ett PersistentState-objekt . MarbleMazeMain-konstruktorn initierar det här objektet och tillhandahåller det lokala programdatalagret som säkerhetskopieringsdatalager.
m_persistentState = ref new PersistentState();
m_persistentState->Initialize(
Windows::Storage::ApplicationData::Current->LocalSettings->Values,
"MarbleMaze");
Marble Maze sparar sitt tillstånd när marmorn passerar över en kontrollpunkt eller målet (i metoden MarbleMazeMain::Update ) och när fönstret förlorar fokus (i metoden MarbleMazeMain::OnFocusChange ). Om ditt spel innehåller en stor mängd tillståndsdata rekommenderar vi att du ibland sparar tillstånd till beständig lagring på ett liknande sätt eftersom du bara har några sekunder på dig att svara på avbrutna meddelanden. När din app får ett pausmeddelande behöver den därför bara spara tillståndsdata som har ändrats.
För att svara på pausa och återuppta meddelanden definierar klassen MarbleMazeMain metoderna SaveState och LoadState som anropas för att pausa och återuppta. Metoden MarbleMazeMain::OnSuspending hanterar paushändelsen och metoden MarbleMazeMain::OnResuming hanterar återuppta-händelsen.
Metoden MarbleMazeMain::OnSuspending sparar speltillståndet och pausar ljudet.
void MarbleMazeMain::OnSuspending()
{
SaveState();
m_audio.SuspendAudio();
}
Metoden MarbleMazeMain::SaveState sparar speltillståndsvärden som marmorns aktuella position och hastighet, den senaste kontrollpunkten och tabellen med höga poäng.
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);
}
}
När spelet återupptas behöver det bara återuppta ljudet. Den behöver inte läsa in tillstånd från beständig lagring eftersom tillståndet redan har lästs in i minnet.
Hur spelet pausar och återupptar ljud förklaras i dokumentet Lägga till ljud till Marble Maze-exemplet.
För att möjliggöra omstart anropar MarbleMazeMain konstruktorn, som anropas under starten, metoden MarbleMazeMain::LoadState. Metoden MarbleMazeMain::LoadState läser och tillämpar tillståndet på spelobjekten. Den här metoden anger även det aktuella speltillståndet till pausat om spelet pausades eller var aktivt när det avbröts. Vi pausar spelet så att användaren inte blir förvånad över oväntad aktivitet. Det flyttas också till huvudmenyn om spelet inte var i ett speltillstånd när det avbröts.
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);
}
}
Viktigt!
Marble Maze skiljer inte mellan kallstart – det vill säga att starta för första gången utan en tidigare paushändelse – och att fortsätta från ett pausat läge. Den här designen rekommenderas för alla UWP-appar.
För mer information om programdata, se Lagra och hämta inställningar och annan appdata.
Nästa steg
Läs Lägga till visuellt innehåll i Marble Maze-exemplet för information om några av de viktigaste metoderna att tänka på när du arbetar med visuella resurser.
Relaterade ämnen
- Lägga till visuellt innehåll i Marble Maze-exempelexemplet
- Grundläggande principer för Marble Maze-exemplet
- Utveckla Marble Maze, ett UWP-spel i C++ och DirectX