Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Megjegyzés:
Ez a témakör a Egyszerű Univerzális Windows Platform (UWP) játék készítése DirectX-szel című oktatóanyag-sorozat része. A hivatkozás témaköre beállítja a sorozat kontextusát.
Miután lefektette a mintajáték alapvető keretrendszerét, és implementált egy állapotgépet, amely kezeli a magas szintű felhasználói és rendszerviselkedéseket, meg kell vizsgálnia azokat a szabályokat és mechanikákat, amelyek a mintajátékot játékká alakítják. Nézzük meg a mintajáték fő objektumának részleteit, és hogy hogyan fordítsunk játékszabályokat interakciókká a játék világával.
Célkitűzések
- Ismerje meg, hogyan alkalmazhat alapszintű fejlesztési technikákat egy UWP DirectX-játék játékszabályainak és mechanikáinak implementálásához.
Fő játékobjektum
A Simple3DGameDX mintajátékban Simple3DGame a fő játékobjektum-osztály. A Simple3DGame egy példánya közvetett módon, a App::Load metóduson keresztül jön létre.
Íme a Simple3DGame osztály néhány funkciója.
- A játékmeneti logika implementálását tartalmazza.
- Olyan metódusokat tartalmaz, amelyek közlik ezeket a részleteket.
- Az alkalmazáskeretben meghatározott állapotgépet érintő játékmenetbeli állapotváltozások.
- A játék állapotának változásai az alkalmazástól a játékobjektumig.
- Részletek a játék felhasználói felületének (átfedés és HUD), az animációk és a fizika frissítéséhez.
Megjegyzés:
A grafikus elemek frissítését a GameRenderer osztály kezeli, amely a játék által használt grafikus eszközök erőforrásainak beszerzésére és használatára szolgáló módszereket tartalmaz. További információ: Renderelési keretrendszer I: Bevezetés a renderelésbe.
- Tárolóként szolgál a játék munkamenetét, szintjét vagy élettartamát meghatározó adatokhoz, attól függően, hogy milyen magas szinten definiálja a játékát. Ebben az esetben a játék állapotadatai a játék teljes élettartamára szólnak, és egy alkalommal inicializálva lesznek, amikor egy felhasználó elindítja a játékot.
Az osztály által definiált metódusok és adatok megtekintéséhez lásd az alábbiakban a Simple3DGame osztályt.
A játék inicializálása és indítása
Amikor egy játékos elindítja a játékot, a játékobjektumnak inicializálnia kell az állapotát, létre kell hoznia és hozzá kell adnia az átfedést, meg kell adnia a játékos teljesítményét nyomon követő változókat, és példányosítania kell azokat az objektumokat, amelyeket a szintek létrehozásához használni fog. Ebben a példában ez akkor történik, amikor a GameMain példány az App::betöltése során jön létre.
A Simple3DGametípusú játékobjektum a GameMain::GameMain konstruktorban jön létre. Ezután a Simple3DGame::Initialize metódus használatával inicializálódik a GameMain::ConstructInBackground „fire-and-forget” korutin során, amelyet a GameMain::GameMainhív meg.
A Simple3DGame::Initialize metódus
A mintajáték beállítja ezeket az összetevőket a játékobjektumban.
- Létrejön egy új hanglejátszó objektum.
- A játék grafikus primitívjeinek tömbjei jönnek létre, beleértve a szint primitívek, a lőszer és az akadályok tömbjeit.
- Létrejön egy hely a játékállapot-adatok mentéséhez, Játéknéven, és az alkalmazásadat-beállítások ApplicationData::Currentáltal megadott tárolási helyre kerül.
- Létrejön egy játék időzítője és a kezdeti, játékon belüli átfedési bitkép.
- Egy új kamera jön létre egy adott nézet- és vetítési paraméterekkel.
- A bemeneti eszköz (a vezérlő) ugyanolyan kezdő dőlésszögre és forgásra van állítva, mint a kamera, így a játékos 1:1 arányú megfeleltetést ér el a kezdő vezérlő pozíciója és a kamera helyzete között.
- A lejátszóobjektum létrejön, és aktívra van állítva. Egy gömb objektumot használunk, hogy észleljük a játékos közelségét a falakhoz és az akadályokhoz, és hogy a kamera ne kerüljön olyan helyzetbe, amely megszakíthatja a merülést.
- A játékvilág alapeleme létrejön.
- A hengerakadályok létre vannak hozva.
- A célok (Face objektumok) létrehozása és számozása.
- A lőszergömbök létrejönnek.
- A szintek létrejönnek.
- A magas pontszám be van töltve.
- Minden korábbi mentett játékállapot betöltődik.
A játéknak most már megvannak az összes kulcsfontosságú összetevőinek példányai - a világ, a játékos, az akadályok, a célok és a lőszergömbök. Emellett a szintek példányai is vannak, amelyek a fenti összetevők konfigurációit és az egyes szintek viselkedését jelölik. Most nézzük meg, hogyan építi a játék a szinteket.
Játékszintek létrehozása és betöltése
A szintépítéshez a legtöbb nehéz munka a mintamegoldás Level[N].h/.cpp mappájában található fájlokban történik. Mivel egy nagyon konkrét megvalósításra összpontosít, itt nem foglalkozunk velük. A lényeg az, hogy az egyes szintek kódja külön Level[N] objektumként fut. Ha ki szeretné terjeszteni a játékot, létrehozhat egy Level[N] objektumot, amely paraméterként egy hozzárendelt számot vesz fel, és véletlenszerűen helyezi el az akadályokat és a célokat. Vagy egy erőforrásfájlból vagy akár az internetről is betöltheti a konfigurációs adatokat.
A játékmenet meghatározása
Most már megvan minden összetevőnk, amire szükségünk van a játék fejlesztéséhez. A szinteket primitívekből a memóriában hozták létre, és készen állnak arra, hogy a játékos megkezdje az interakciót.
A legjobb játékok azonnal reagálnak a játékos bemenetére, és azonnali visszajelzést adnak. Ez minden típusú játékra igaz, a gyors reflexeket igénylő akciójátékoktól és a valós idejű, első személyű lövöldözős játékoktól az átgondolt, körökre osztott stratégiai játékokig.
A Simple3DGame::RunGame metódus
Amíg a játékszint folyamatban van, a játék Dynamics állapotban van.
GameMain::Update a fő frissítési ciklus, amely keretenként egyszer frissíti az alkalmazás állapotát, ahogy az alább látható. A frissítési ciklus meghívja a Simple3DGame::RunGame metódust a munka kezeléséhez, ha a játék a Dynamics állapotban van.
// 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 kezeli azokat az adatokat, amelyek meghatározzák a játék aktuális állapotát a játék hurok aktuális iterációjához.
Itt van a játék folyamat logikája Simple3DGame::RunGame.
- A metódus frissíti az időzítőt, amely a szint befejezéséig számítja a másodperceket, és ellenőrzi, hogy a szint ideje lejárt-e. Ez a játék egyik szabálya – ha elfogy az idő, ha nem minden célpontot lőttek le, akkor vége a játéknak.
- Ha az idő elfogy, a metódus beállítja a TimeExpired játék állapotát, és visszatér az előző kód Frissítés metódusához.
- Ha marad idő, akkor a mozgatás-kinézés vezérlőből lekérdezik a kamera pozíciójának frissítését; konkrétan, a kameraplánból kiinduló nézeti normál szögének frissítését (ahova a játékos néz), valamint azt a távolságot, amennyit ez a szög elmozdult azóta, amióta utoljára lekérdezték a vezérlőt.
- A kamera az új adatai alapján frissül a mozgás-nézet vezérlőnek.
- A játék világában lévő objektumok dinamikája, illetve animációi és viselkedése a játékosok irányításától függetlenül frissül. Ebben a mintajátékban a Simple3DGame::UpdateDynamics metódust hívják meg, hogy frissítse a kilőtt lőszergömbök mozgását, a pillérakadályok animációját és a célpontok mozgását. További információ: A játék világának frissítése.
- A módszer ellenőrzi, hogy teljesültek-e a szint sikeres teljesítésének feltételei. Ha igen, véglegesíti a szint pontszámát, és ellenőrzi, hogy ez-e az utolsó szint (a 6-ból). Ha ez az utolsó szint, akkor a metódus a GameState::GameComplete játékállapotát adja vissza; ellenkező esetben a GameState::LevelComplete játék állapotát adja vissza.
- Ha a szint nem fejeződött be, akkor a metódus a játék állapotát GameState::Activeértékre állítja, és visszaadja.
A játék világának frissítése
Amikor a játék fut, ebben a példában a Simple3DGame::UpdateDynamics metódust a Simple3DGame::RunGame metódus hívja meg (amelyet viszont a GameMain::Updatehív) a játék jelenetében renderelt objektumok frissítésére.
Egy olyan hurok, mint a UpdateDynamics meghív minden olyan módszert, amely a játék világának mozgásba hozásához használatos, függetlenül a játékos bemenetétől, hogy magával ragadó játékélményt hozzon létre, és életre keltse a szintet. Ez magában foglalja a renderelendő grafikus elemeket, valamint animációs hurkok futtatását, hogy dinamikus világot teremtsen még akkor is, ha nincs lejátszó bemenete. A játékban, ez magában foglalhatja a szélben hajladozó fákat, a partvonalak mentén megtörő hullámokat, a füstölő gépeket, és az idegen szörnyeket, amelyek nyújtózkodnak és mozognak. Magában foglalja az objektumok közötti interakciót is, beleértve a játékos szférája és a világ közötti ütközéseket, vagy a lőszer és az akadályok és célok közötti ütközéseket.
Kivéve, ha a játék kifejezetten szüneteltetve van, a játék huroknak folytatnia kell a játék világának frissítését; akár játéklogikán, fizikai algoritmusokon, akár egyszerű véletlenszerűen.
A mintajátékban ezt az alapelvet dynamicsnevezik, és magában foglalja a pillér akadályainak emelkedését és bukását, valamint a lőszer gömbök mozgását és fizikai viselkedését, miközben aktiválódnak és mozgásban vannak.
A Simple3DGame::UpdateDynamics metódus
Ez a módszer ezzel a négy számításkészlettel foglalkozik.
- A világ kirúgott lőszergömbjeinek pozíciói.
- A pillér akadályainak animációja.
- A játékos és a világhatárok metszete.
- A lőszergömbök ütközése az akadályokkal, a célokkal, más lőszergömbökkel és a világgal.
Az akadályok animációja a Animate.h/.cpp forráskódfájlokban meghatározott hurokban történik. A lőszer viselkedését és az ütközéseket egyszerűsített fizikai algoritmusok határozzák meg, melyek a kódban vannak megadva, és a játékvilág globális konstansai, beleértve a gravitációt és az anyagtulajdonságokat is, paraméterezték. Ez mind a játék világában koordináta-térben van kiszámítva.
A folyamat áttekintése
Most, hogy frissítettük a jelenet összes objektumát, és kiszámítottuk az ütközéseket, ezeket az adatokat kell használnunk a megfelelő vizuális módosítások rajzolásához.
Miután GameMain::Update befejezte a játék ciklusának aktuális iterációját, a minta azonnal meghívja GameRenderer::Render, hogy a frissített objektumadatokat vegye át, és hozzon létre egy új jelenetet a játékos számára, ahogy alább látható.
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.
}
A játék világának ábráinak megjelenítése
Azt javasoljuk, hogy a játék grafikái gyakran frissülnek, ideális esetben pontosan olyan gyakran, mint a fő játék hurok iterálása. Amint a ciklus fut, a játék világának állapota frissül, játékos bemenettel vagy anélkül. Ez lehetővé teszi a számított animációk és viselkedések zökkenőmentes megjelenítését. Képzelje el, ha volt egy egyszerű vízjelenetünk, amely csak akkor mozdult el, amikor a játékos megnyomott egy gombot. Ez nem lenne reális; egy jó játék mindig sima és folyamatosnak tűnik.
Emlékezzen vissza a példajáték hurokjára, ahogy azt a GameMain::Runmutatja. Ha a játék főablaka látható, és nincs rögzítve vagy inaktiválva, akkor a játék továbbra is frissíti és vizualizálja a frissítések eredményeit. A GameRenderer::Render metódus, amelyet a következőben megvizsgálunk, ennek az állapotnak a megjelenítését jeleníti meg. Ez közvetlenül a GameMain::Updatemeghívását követően történik, beleértve a Simple3DGame::RunGame hívást is az állapotok frissítése érdekében, ahogy az az előző szakaszban ismertetésre került.
GameRenderer::Render rajzolja a 3D-s világ vetületét, majd rárajzolja a Direct2D átfedést. Ha elkészült, megjeleníti a végső felcserélési láncot a megjelenítendő kombinált pufferekkel.
Megjegyzés:
A mintajáték Direct2D átfedésének két állapota van– az egyik, amelyben a játék megjeleníti a szünet menü bitképét tartalmazó játékinformációs átfedést, az egyikben pedig a játék megjeleníti a célkereszteket, valamint az érintőképernyős mozgásvezérlő téglalapjait. A pontszám szövege mindkét állapotban meg van rajzolva. További információ: Renderelési keretrendszer I: Bevezetés a renderelésbe.
A *GameRenderer::Render* metódus
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
);
}
...
}
}
A Simple3DGame osztály
Ezek az Simple3DGame osztály által meghatározott metódusok és adattagok.
Tagfüggvények
A Simple3DGame által definiált nyilvános tagfüggvények az alábbiakat tartalmazzák.
- inicializálása:. Beállítja a globális változók kezdőértékeit, és inicializálja a játékobjektumokat. Ez a Inicializálás és a játék indítása szakaszban található.
- LoadGame. Inicializál egy új szintet, és elkezdi betölteni.
- LoadLevelAsync. Egy koroutin, amely inicializálja a szintet, majd meghív egy másik koroutint a renderelőn az eszközspecifikus szintű erőforrások betöltéséhez. Ez a metódus külön szálon fut; Ennek eredményeképpen ebből a szálból csak ID3D11Device metódusok hívhatók meg (szemben a ID3D11DeviceContext metódusokkal). Az eszközkörnyezeti metódusokat a FinalizeLoadLevel metódus hívja meg. Ha még nem rendelkezik aszinkron programozással, tekintse meg C++/WinRTegyidejűségi és aszinkron műveleteket.
- FinalizeLoadLevel. Befejezi a szint betöltéséhez szükséges minden munkát, amelyet a főszálon el kell végezni. Ez magában foglalja a Direct3D 11 eszközkörnyezetbe (ID3D11DeviceContext) irányuló hívásokat.
- StartLevel. Új szintre emeli a játékmenetet.
- PauseGame. Szünetelteti a játékot.
- RunGame. A játékhurok iterációját futtatja. A App::Update egyszer kerül meghívásra minden ismétléskor a játék hurokban, ha a játék állapota Aktív.
- OnSuspending és OnResuming. A játék hangjának felfüggesztése/folytatása.
Íme a magán tagfüggvények.
- LoadSavedState és SaveState. Töltse be/mentse a játék aktuális állapotát.
- LoadHighScore és SaveHighScore. Mentse és töltse be a játékokban elért legmagasabb pontszámot.
- InicializálásAmmo. Minden lőszerként használt gömbobjektum állapotát visszaállítja az eredeti állapotára az egyes körök elejére.
- UpdateDynamics. Ez egy fontos módszer, mert frissíti az összes játékobjektumot az előre meghatározott animációs rutinok, a fizika és a vezérlési bemenet alapján. Ez az interaktivitás lényege, amely meghatározza a játékot. Ez a A játék világának frissítése szakaszban található.
A többi nyilvános metódus olyan tulajdonság kiegészítő, amely játékmeneti és átfedésspecifikus információkat ad vissza az alkalmazás-keretrendszernek megjelenítésre.
Adattagok
Ezek az objektumok a játékmenet főciklusának futása közben frissülnek.
- MoveLookController objektum. A játékos bemenetét jelöli. További információ: Vezérlők hozzáadása.
- GameRenderer objektumot. Egy Direct3D 11-megjelenítőt jelöl, amely kezeli az összes eszközspecifikus objektumot és azok renderelését. További információ: Rendering framework I.
- Hang objektum. Szabályozza a játék hanglejátszását. További információért lásd: Hang hozzáadása.
A többi játékváltozó tartalmazza a primitívek listáját és azok játékon belüli mennyiségét, valamint a játékban meghatározott adatokat és korlátozásokat.
Következő lépések
Még nem beszéltünk a tényleges renderelési motorról – arról, hogyan alakulnak át a frissített primitívek Render metódusainak hívásai képpontokká a képernyőn. Ezek a szempontok két részből állnak:Rendering framework I: Intro to rendering és Rendering framework II: Game rendering. Ha jobban érdekli a játékosvezérlők játékállapotának frissítése, tekintse meg Vezérlők hozzáadásacímű témakört.