Anteckning
Å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.
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.
När du har lagt fram det grundläggande ramverket för exempelspelet och implementerat en tillståndsdator som hanterar användar- och systembeteenden på hög nivå, vill du undersöka de regler och mekanik som omvandlar exempelspelet till ett spel. Nu ska vi titta på detaljerna i exempelspelets huvudobjekt och hur du översätter spelregler till interaktioner med spelvärlden.
Målsättningar
- Lär dig hur du använder grundläggande utvecklingstekniker för att implementera spelregler och mekanik för ett UWP DirectX-spel.
Huvudspelobjekt
I Simple3DGameDX-exempelspelet är Simple3DGame huvudklassen för spelobjektet. En instans av Simple3DGame konstrueras indirekt via metoden App::Load .
Här är några av funktionerna i klassen Simple3DGame .
- Innehåller implementering av spellogik.
- Innehåller metoder som förmedlar den här informationen.
- Ändringar i speltillståndet i tillståndsmaskinen definierad i appramverket.
- Ändringar i speltillståndet från appen till själva spelobjektet.
- Information om hur du uppdaterar spelets användargränssnitt (överlägg och heads-up display), animeringar och fysik (dynamiken).
Anmärkning
Uppdatering av grafik hanteras av klassen GameRenderer , som innehåller metoder för att hämta och använda grafikenhetsresurser som används av spelet. Mer information finns i Renderingsramverk I: Introduktion till rendering.
- Fungerar som en container för data som definierar en spelsession, nivå eller livslängd, beroende på hur du definierar ditt spel på en hög nivå. I det här fallet är speltillståndsdata för spelets livslängd och initieras en gång när en användare startar spelet.
Information om hur du visar de metoder och data som definierats av den här klassen finns i klassen Simple3DGame nedan.
Initiera och starta spelet
När en spelare startar spelet måste spelobjektet initiera dess tillstånd, skapa och lägga till överlägget, ange variablerna som spårar spelarens prestanda och instansiera de objekt som ska användas för att skapa nivåerna. I det här exemplet görs detta när GameMain-instansen skapas i App::Load.
Spelobjektet, av typen Simple3DGame, skapas i konstruktorn GameMain::GameMain . Den initieras med hjälp av Simple3DGame::Initialize-metoden under GameMain::ConstructInBackground fire-and-forget coroutine, som sedan anropas från GameMain::GameMain.
Metoden för Simple3DGame::Initialize
Exempelspelet konfigurerar dessa komponenter i spelobjektet.
- Ett nytt ljuduppspelningsobjekt skapas.
- Matriser för spelets grafiska primitiver skapas, inklusive matriser för nivåprimitiver, ammunition och hinder.
- En plats för att spara speltillståndsdata skapas med namnet Spel och placeras på lagringsplatsen för appdatainställningar som anges av ApplicationData::Current.
- En speltimer och den första bitmappen för överlägg i spelet skapas.
- En ny kamera skapas med en specifik uppsättning vy- och projektionsparametrar.
- Indataenheten (styrenheten) är inställd på samma starthöjning och gir som kameran, så spelaren har en 1-till-1-korrespondens mellan startkontrollpositionen och kamerapositionen.
- Spelarobjektet skapas och anges till aktivt. Vi använder ett sfärobjekt för att upptäcka om spelaren är i närheten av väggar och hinder och för att hindra kameran från att placeras i en position som kan störa inlevelsen.
- Ett primitivt objekt i spelvärlden har skapats.
- Cylinderhindrarna skapas.
- Målen (Face-objekt) skapas och numreras.
- Ammunitionssfärerna skapas.
- Nivåerna skapas.
- Den höga poängen läses in.
- Alla tidigare sparade speltillstånd läses in.
Spelet har nu instanser av alla viktiga komponenter – världen, spelaren, hindren, målen och ammunitionssfärerna. Den har också instanser av nivåerna, som representerar konfigurationer av alla ovanstående komponenter och deras beteenden för varje specifik nivå. Nu ska vi se hur spelet bygger nivåerna.
Skapa och ladda spelnivåer
De flesta av de tunga lyften för nivåkonstruktionen görs i de Level[N].h/.cpp-filer som finns i mappen GameLevels i exempellösningen. Eftersom den fokuserar på en mycket specifik implementering kommer vi inte att täcka dem här. Det viktiga är att koden för varje nivå körs som ett separat Nivå[N] -objekt. Om du vill utöka spelet kan du skapa ett Level[N] -objekt som tar ett tilldelat nummer som en parameter och slumpmässigt placerar hinder och mål. Eller så kan du låta det ladda konfigurationsdata för laddningsnivå från en resursfil, eller till och med från internet.
Definiera spelupplägget
Nu har vi alla komponenter vi behöver för att utveckla spelet. Nivåerna har konstruerats med primitivelementen i åtanke och är redo för spelaren att börja interagera med.
De bästa spelen reagerar direkt på spelarens indata och ger omedelbar feedback. Detta gäller för alla typer av spel, från twitch-action, realtidsskjutare till tankeväckande, turbaserade strategispel.
Metoden Simple3DGame::RunGame
Medan en spelnivå är igång befinner sig spelet i tillståndet Dynamics.
GameMain::Update är huvuduppdateringsloopen som uppdaterar programtillståndet en gång per bildruta, enligt nedan. Uppdateringsloopen anropar metoden Simple3DGame::RunGame för att hantera arbetet om spelet är i Dynamics-tillstånd .
// 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 hanterar datauppsättningen som definierar spelets aktuella tillstånd för den aktuella iterationen av spelslingan.
Här är spelflödeslogik i Simple3DGame::RunGame.
- Metoden uppdaterar timern som räknar ned sekunderna tills nivån har slutförts och testar om nivåns tid har upphört att gälla. Detta är en av spelets regler – när tiden tar slut, om inte alla mål har skjutits, så är det spel över.
- Om tiden har tagit slut anger metoden timeexpired-speltillståndet och återgår till metoden Update i föregående kod.
- Om det finns tid kvar avsöks styrenheten för move-look för en uppdatering av kamerapositionen; mer specifikt, en uppdatering av vinkeln för den normala sikten som projiceras från kameraplanet (där spelaren tittar), och avståndet som vinkeln har rört sig sedan styrenheten avsökts senast.
- Kameran uppdateras baserat på nya data från move-look-kontrollanten.
- Dynamiken, eller animeringar och beteenden för objekt i spelvärlden oberoende av spelarkontroll, uppdateras. I det här exempelspelet anropas metoden Simple3DGame::UpdateDynamics för att uppdatera rörelserna i de ammunitionssfärer som har avfyrats, animeringen av pelarhinder och målens rörelse. Mer information finns i Uppdatera spelvärlden.
- Metoden kontrollerar om kriterierna för att slutföra en nivå har uppfyllts. I så fall fastställs poängen för nivån, och det kontrolleras om det här är den sista nivån (av 6). Om det är den sista nivån returnerar metoden speltillståndet GameState::GameComplete ; annars returneras speltillståndet GameState::LevelComplete .
- Om nivån inte är klar anger metoden speltillståndet till GameState::Active och returnerar.
Uppdatera spelvärlden
I det här exemplet anropas metoden Simple3DGame::UpdateDynamics från metoden Simple3DGame::RunGame (som anropas från GameMain::Update) för att uppdatera de objekt som återges i en spelscen.
En loop som UpdateDynamics anropar alla metoder som används för att sätta spelvärlden i rörelse, oberoende av spelarens indata, för att skapa en uppslukande spelupplevelse och göra nivån levande. Detta omfattar grafik som måste återges och animeringsslingor som körs för att skapa en dynamisk värld även om det inte finns några spelarindata. I ditt spel kan det inkludera träd som svajar i vinden, vågor som bryts längs strandlinjer, maskiner som ryker och främmande monster som sträcker sig och rör sig runt. Det omfattar också interaktionen mellan objekt, inklusive kollisioner mellan spelarsfären och världen, eller mellan ammunitionen och hindren och målen.
Förutom när spelet är specifikt pausat bör spelslingan fortsätta att uppdatera spelvärlden. oavsett om det är baserat på spellogik, fysiska algoritmer eller om det bara är slumpmässigt.
I exempelspelet kallas den här principen dynamikoch den omfattar pelarhindrens uppgång och fall samt projektilkulornas rörelse och fysiska beteenden när de avfyras och är i rörelse.
Simple3DGame::UpdateDynamics-metoden
Den här metoden hanterar dessa fyra uppsättningar beräkningar.
- Positionerna för de avfyrade ammunitionssfärerna i världen.
- Animeringen av pelarhinder.
- Skärningspunkten mellan spelaren och världens gränser.
- Kollisionerna mellan ammunitionssfärer och hinder, mål, andra ammunitionssfärer och världen.
Animeringen av hindren sker i en loop som definieras i källkodsfilerna Animate.h/.cpp . Ammunitionens beteende och eventuella kollisioner definieras av förenklade fysikalgoritmer, som tillhandahålls i koden och parametriseras av en uppsättning globala konstanter för spelvärlden, inklusive gravitations- och materialegenskaper. Allt detta beräknas i spelvärldens koordinatutrymme.
Granska flödet
Nu när vi har uppdaterat alla objekt i scenen och beräknat eventuella kollisioner måste vi använda den här informationen för att rita motsvarande visuella ändringar.
När GameMain::Update har slutfört den aktuella iterationen av spelloopen anropar exemplet omedelbart GameRenderer::Render för att ta uppdaterade objektdata och generera en ny scen som ska presenteras för spelaren, enligt nedan.
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.
}
Rendera spelvärldens grafik
Vi rekommenderar att grafiken i ett spel uppdateras ofta, helst exakt lika ofta som huvudspelsloopen itererar. När loopen itererar uppdateras spelvärldens tillstånd, med eller utan spelarindata. På så sätt kan beräknade animeringar och beteenden visas smidigt. Tänk om vi hade en enkel scen med vatten som bara rörde sig när spelaren tryckte på en knapp. Det skulle inte vara realistiskt; ett bra spel ser smidigt och flytande hela tiden.
Kom ihåg exempelspelets loop enligt ovan i GameMain::Run. Om spelets huvudfönster är synligt och inte har snappats eller inaktiverats fortsätter spelet att uppdatera och återge resultatet av uppdateringen. Metoden GameRenderer::Render som vi undersöker härnäst återger en representation av det tillståndet. Detta görs omedelbart efter ett anrop till GameMain::Update, som innehåller Simple3DGame::RunGame för att uppdatera tillstånd, enligt beskrivningen i föregående avsnitt.
GameRenderer::Render ritar projektionen av 3D-världen och drar sedan Direct2D-överlägget ovanpå den. När det är slutfört presenterar det den slutliga växlingskedjan med de kombinerade buffertarna för visning.
Anmärkning
Det finns två tillstånd för exempelspelets Direct2D-överlägg – ett där spelet visar spelinformationsöverlägget som innehåller bitmappen för pausmenyn och en där spelet visar hårkorset tillsammans med rektanglarna för pekskärmens move-look-kontrollant. Poängtexten ritas i båda lägena. Mer information finns i Rendering framework I: Intro to rendering (Återgivningsramverk I: Introduktion till rendering).
Metoden Render i GameRenderer
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
);
}
...
}
}
Klassen Simple3DGame
Det här är de metoder och datamedlemmar som definieras av klassen Simple3DGame .
Medlemsfunktioner
Offentliga medlemsfunktioner som definieras av Simple3DGame innehåller de som anges nedan.
- Initiera. Anger startvärdena för de globala variablerna och initierar spelobjekten. Detta beskrivs i avsnittet Initiera och starta spelet .
- LoadGame. Initierar en ny nivå och börjar läsa in den.
- LoadLevelAsync. En korutin som initierar nivån och sedan anropar en annan korutin på renderaren för att läsa in de enhetsspecifika nivåresurserna. Den här metoden körs i en separat tråd. Därför kan endast ID3D11Enhetsmetoder (i motsats till ID3D11DeviceContext-metoder ) anropas från den här tråden. Alla metoder för enhetskontext anropas i metoden FinalizeLoadLevel . Om du är nybörjare på asynkron programmering kan du läsa Samtidighet och asynkrona åtgärder med C++/WinRT.
- SlutförLoadLevel. Slutför allt arbete för nivåladdning som måste utföras på huvudtråden. Detta inkluderar alla anrop till Direct3D 11-enhetskontextmetoder (ID3D11DeviceContext).
- StartLevel. Startar spelet för en ny nivå.
- PauseGame. Pausar spelet.
- RunGame. Kör en iteration av spelloopen. Den anropas från App::Uppdatera en gång varje iteration av spelloopen om speltillståndet är Active.
- "OnSuspending" och "OnResuming". Pausa/återuppta spelets ljud.
Här är de privata medlemsfunktionerna.
- LoadSavedState och SaveState. Ladda in respektive spara spelets aktuella tillstånd.
- LoadHighScore och SaveHighScore. Läs in/spara höga poäng för respektive spel.
- InitieraAmmo Återställer tillståndet för varje sfärobjekt som används som ammunition tillbaka till sitt ursprungliga tillstånd för början av varje runda.
- UpdateDynamics. Det här är en viktig metod eftersom den uppdaterar alla spelobjekt baserat på rutiner för konserverad animering, fysik och kontrollindata. Detta är hjärtat i interaktiviteten som definierar spelet. Detta beskrivs i avsnittet Uppdatera spelvärlden .
De andra offentliga metoderna är metoder för att komma åt egenskaper som returnerar spel- och överlagringsspecifik information till appramverket för visning.
Datamedlemmar
Dessa objekt uppdateras när spelslingan körs.
- MoveLookController objekt. Representerar spelarens indata. Mer information finns i Lägga till kontroller.
- GameRenderer objekt. Representerar en Direct3D 11-renderare som hanterar alla enhetsspecifika objekt och deras återgivning. Mer information finns i Rendering framework I.
- ljud-objekt. Styr ljuduppspelningen för spelet. För mer information, se Lägga till ljud.
Resten av spelvariablerna innehåller listorna över primitiverna och deras respektive spelbelopp och spelspecifika data och begränsningar.
Nästa steg
Vi har ännu inte pratat om den faktiska återgivningsmotorn – hur anrop till renderningsmetoderna på de uppdaterade primitiverna omvandlas till bildpunkter på skärmen. Dessa aspekter beskrivs i två delar–Renderingsramverk I: Intro till rendering och Renderingsramverk II: Spelrendering. Om du är mer intresserad av hur spelarkontrollerna uppdaterar speltillståndet kan du läsa Lägga till kontroller.