Megosztás a következőn keresztül:


A fő játékobjektum meghatározása

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.