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.
Most, hogy a játék 3D-s vizualizációkkal rendelkezik, ideje néhány 2D-s elem hozzáadására összpontosítani, hogy a játék visszajelzést küldjön a játék állapotáról a játékosnak. Ez úgy érhető el, hogy egyszerű menüopciókat és head-up kijelző összetevőket ad hozzá a 3D grafikai adatfolyam kimenetéhez.
Megjegyzés:
Ha még nem töltötte le a minta legújabb játékkódját, lépjen tovább a Direct3D minta játék-ra. Ez a minta az UWP-szolgáltatásminták nagy gyűjteményének része. A minta letöltésére vonatkozó utasításokért lásd a Windows-fejlesztéshez készült mintaalkalmazásokat.
Célkitűzés
A Direct2D használatával adjon hozzá számos felhasználói felületi ábrát és viselkedést az UWP DirectX-játékhoz, beleértve a következőket:
- Fejfelfedő kijelző, beleértve az áthelyezési vezérlő határ téglalapjait
- Játékállapot menük
A felhasználói felület átfedése
Bár a Szöveg és a felhasználói felület elemei számos módon jeleníthetők meg egy DirectX-játékban, Direct2D-fogunk összpontosítani. A szövegelemekhez DirectWrite is használunk.
A Direct2D a képpontalapú primitívek és effektusok rajzolására használt 2D rajz API-k készlete. A Direct2D-vel való kezdéskor a legjobb, ha egyszerűnek tartja a dolgokat. Az összetett elrendezésekhez és felületi viselkedésekhez időre és tervezésre van szükség. Ha a játékhoz összetett felhasználói felületre van szükség, például a szimulációban és a stratégiai játékokban találhatóakhoz, fontolja meg inkább az XAML használatát.
Megjegyzés:
További információ a felhasználói felület XAML-sel való fejlesztéséről egy UWP DirectX-játékban: A mintajáték kiterjesztése.
A Direct2D nem kifejezetten felhasználói felületekhez vagy elrendezésekhez, például HTML-hez és XAML-hez készült. Nem biztosít felhasználói felületi összetevőket, például listákat, dobozokat vagy gombokat. Emellett nem biztosít olyan elrendezési összetevőket, mint a divs, a table vagy a grid.
Ebben a mintajátékban két fő felhasználói felületi összetevőt használunk.
- A pontszám és a játékon belüli vezérlők áttekinthető kijelzője.
- A játékállapot szövegének és beállításainak, például a szüneteltetési adatoknak és a szintindítási beállításoknak a megjelenítésére szolgáló átfedés.
A Direct2D használata felfelé irányuló kijelzőhöz
Az alábbi képen a minta játékon belüli fej-up kijelzője látható. Egyszerű és áttekinthetetlen, így a játékos a 3D-s világra összpontosíthat, és célokat lőhet ki. A jó interfészek vagy heads-up kijelzők soha nem bonyolíthatják a játékosok képességét arra, hogy feldolgozzák és reagáljanak a játék eseményeire.
Az átfedés a következő alapvető primitívekből áll.
-
DirectWrite szöveget a jobb felső sarokban, amely tájékoztatja a játékost a játékmenetről.
- Sikeres találatok
- A játékos által készített lövések száma
- A szint hátralévő ideje
- Aktuális szintszám
- Két egymást keresztező vonalszegmens, amelyeket keresztszőrzetek alkotnak
- Két téglalap az alsó sarkokban a mozgatás-nézet vezérlő határaihoz.
Az átfedés játékon belüli megjelenítési állapotát a GameHud::Render
Ha a játék inicializálva lett, TotalHits(), TotalShots()és TimeRemaining() hozzáadjuk egy swprintf_s pufferhez, majd megadjuk a nyomtatási formátumot. Ezután megrajzolhatjuk a DrawText metódussal. Ugyanezt tesszük az aktuális szintjelző esetében is, üres számokat rajzolva megjelenítjük a nem befejezett szinteket, például a ➀- és a kitöltött számokat, például a ➊, hogy jelezzük, hogy az adott szint befejeződött.
A következő kódrészlet végigvezeti a GameHud::Render metódus folyamatát a következőhöz:
- Bitkép létrehozása **ID2D1RenderTarget::DrawBitmap **
- A felhasználói felületi területek téglalapokra való tagolása D2D1::RectF
- Szövegelemek készítése a DrawText használatával
void GameHud::Render(_In_ std::shared_ptr<Simple3DGame> const& game)
{
auto d2dContext = m_deviceResources->GetD2DDeviceContext();
auto windowBounds = m_deviceResources->GetLogicalSize();
if (m_showTitle)
{
d2dContext->DrawBitmap(
m_logoBitmap.get(),
D2D1::RectF(
GameUIConstants::Margin,
GameUIConstants::Margin,
m_logoSize.width + GameUIConstants::Margin,
m_logoSize.height + GameUIConstants::Margin
)
);
d2dContext->DrawTextLayout(
Point2F(m_logoSize.width + 2.0f * GameUIConstants::Margin, GameUIConstants::Margin),
m_titleHeaderLayout.get(),
m_textBrush.get()
);
d2dContext->DrawTextLayout(
Point2F(GameUIConstants::Margin, m_titleBodyVerticalOffset),
m_titleBodyLayout.get(),
m_textBrush.get()
);
}
// Draw text for number of hits, total shots, and time remaining
if (game != nullptr)
{
// This section is only used after the game state has been initialized.
static const int bufferLength = 256;
static wchar_t wsbuffer[bufferLength];
int length = swprintf_s(
wsbuffer,
bufferLength,
L"Hits:\t%10d\nShots:\t%10d\nTime:\t%8.1f",
game->TotalHits(),
game->TotalShots(),
game->TimeRemaining()
);
// Draw the upper right portion of the HUD displaying total hits, shots, and time remaining
d2dContext->DrawText(
wsbuffer,
length,
m_textFormatBody.get(),
D2D1::RectF(
windowBounds.Width - GameUIConstants::HudRightOffset,
GameUIConstants::HudTopOffset,
windowBounds.Width,
GameUIConstants::HudTopOffset + (GameUIConstants::HudBodyPointSize + GameUIConstants::Margin) * 3
),
m_textBrush.get()
);
// Using the unicode characters starting at 0x2780 ( ➀ ) for the consecutive levels of the game.
// For completed levels start with 0x278A ( ➊ ) (This is 0x2780 + 10).
uint32_t levelCharacter[6];
for (uint32_t i = 0; i < 6; i++)
{
levelCharacter[i] = 0x2780 + i + ((static_cast<uint32_t>(game->LevelCompleted()) == i) ? 10 : 0);
}
length = swprintf_s(
wsbuffer,
bufferLength,
L"%lc %lc %lc %lc %lc %lc",
levelCharacter[0],
levelCharacter[1],
levelCharacter[2],
levelCharacter[3],
levelCharacter[4],
levelCharacter[5]
);
// Create a new rectangle and draw the current level info text inside
d2dContext->DrawText(
wsbuffer,
length,
m_textFormatBodySymbol.get(),
D2D1::RectF(
windowBounds.Width - GameUIConstants::HudRightOffset,
GameUIConstants::HudTopOffset + (GameUIConstants::HudBodyPointSize + GameUIConstants::Margin) * 3 + GameUIConstants::Margin,
windowBounds.Width,
GameUIConstants::HudTopOffset + (GameUIConstants::HudBodyPointSize + GameUIConstants::Margin) * 4
),
m_textBrush.get()
);
if (game->IsActivePlay())
{
// Draw the move and fire rectangles
...
// Draw the crosshairs
...
}
}
}
A módszer tovább bontásával ez a GameHud::Render metódus része rajzolja meg a mozgatás és tűz téglalapokat a ID2D1RenderTarget::DrawRectanglesegítségével, és a célkeresztet két hívás segítségével az ID2D1RenderTarget::DrawLine-én.
// Check if game is playing
if (game->IsActivePlay())
{
// Draw a rectangle for the touch input for the move control.
d2dContext->DrawRectangle(
D2D1::RectF(
0.0f,
windowBounds.Height - GameUIConstants::TouchRectangleSize,
GameUIConstants::TouchRectangleSize,
windowBounds.Height
),
m_textBrush.get()
);
// Draw a rectangle for the touch input for the fire control.
d2dContext->DrawRectangle(
D2D1::RectF(
windowBounds.Width - GameUIConstants::TouchRectangleSize,
windowBounds.Height - GameUIConstants::TouchRectangleSize,
windowBounds.Width,
windowBounds.Height
),
m_textBrush.get()
);
// Draw the cross hairs
d2dContext->DrawLine(
D2D1::Point2F(windowBounds.Width / 2.0f - GameUIConstants::CrossHairHalfSize,
windowBounds.Height / 2.0f),
D2D1::Point2F(windowBounds.Width / 2.0f + GameUIConstants::CrossHairHalfSize,
windowBounds.Height / 2.0f),
m_textBrush.get(),
3.0f
);
d2dContext->DrawLine(
D2D1::Point2F(windowBounds.Width / 2.0f, windowBounds.Height / 2.0f -
GameUIConstants::CrossHairHalfSize),
D2D1::Point2F(windowBounds.Width / 2.0f, windowBounds.Height / 2.0f +
GameUIConstants::CrossHairHalfSize),
m_textBrush.get(),
3.0f
);
}
A GameHud::Render metódusban a játékablak logikai méretét a windowBounds változóban tároljuk. Ez a GetLogicalSize osztály metódusát használja.
auto windowBounds = m_deviceResources->GetLogicalSize();
A felhasználói felület programozásához elengedhetetlen a játékablak méretének beszerzése. Az ablak méretét a DIP-k (eszközfüggetlen képpontok) nevű mérés adja meg, ahol a DIP egy hüvelyk 1/96-aként van definiálva. A Direct2D a rajz bekövetkezésekor tényleges képpontokra skálázza a rajzegységeket, ehhez használja a Windows pont/hüvelyk (DPI) beállítást. Hasonlóképpen, ha DirectWritehasználatával rajzol szöveget, a betűtípus méretére vonatkozó pontok helyett a DIP-ket kell megadnia. A DIP-k lebegőpontos számokként vannak kifejezve.
A játék állapotinformációinak megjelenítése
A heads-up kijelző mellett a mintajátéknak van egy átfedése, amely hat játékállapotot jelöl. Minden állam egy nagy fekete téglalap alapelemet tartalmaz, szöveggel, amit a játékos elolvashat. A move-look vezérlő téglalapjai és célkeresztjei nem rajzolódnak ki, mert ezek az állapotok nem aktívak.
Az átfedés a GameInfoOverlay osztály használatával jön létre, lehetővé téve számunkra, hogy kikapcsoljuk a megjelenő szöveget, hogy igazodjunk a játék állapotához.
állapota és művelete
Az átfedés két szakaszra oszlik: Állapot és Művelet. Az Állapot szakasz további bontásban a Cím és a Törzs téglalapokra oszlik. A Művelet szakaszban csak egy téglalap van. Minden téglalapnak más a rendeltetése.
-
titleRectangletartalmazza a címszöveget. -
bodyRectangletartalmazza a szövegtörzset. -
actionRectangletartalmazza azt a szöveget, amely tájékoztatja a játékost egy adott művelet végrehajtásáról.
A játék hat állapotban állítható be. A játék állapotát a Állapot átfedés részével adják át. A Állapot téglalapok a következő állapotoknak megfelelő számos metódussal frissülnek.
- Betöltés
- Kezdeti kezdési/magas pontszámú statisztikák
- Szint kezdete
- Játék szüneteltetve
- Vége a játéknak
- Játék nyert
Az átfedés művelet része a GameInfoOverlay::SetAction metódussal frissül, így a művelet szövege az alábbiak egyikére állítható be.
- "Koppintson újra a lejátszáshoz..."
- Kérjük, várjon, amíg a szint betöltődik...
- "Koppintson a folytatáshoz..."
- Egyik sem
Megjegyzés:
Mindkét módszerről bővebben a A játékállapot című szakaszban lesz szó.
A játékban történtektől függően a Állapot és Művelet szakasz szövegmezői módosulnak. Nézzük meg, hogyan inicializáljuk és rajzoljuk meg a hat állam átfedését.
A fedvény inicializálása és rajzolása
A hat státusz államának van néhány közös jellemzője, így az általuk igényelt erőforrások és módszerek nagyon hasonlóak. - Mind fekete téglalapot használnak a képernyő közepén háttérként. - A megjelenített szöveg vagy Cím vagy Törzs szöveg. - A szöveg a Segoe felhasználói felület betűtípusát használja, és a hátsó téglalapra van rajzolva.
A mintajáték négy metódust kínál, amelyek az átfedés létrehozásakor kerülnek játékba.
GameInfoOverlay::GameInfoOverlay
A GameInfoOverlay::GameInfoOverlay konstruktor inicializálja az átfedést, fenntartva a bitképfelületet, amelyen adatokat jelenítünk meg a játékos számára. A konstruktor a neki átadott ID2D1Device objektumból szerez meg egy gyárat, amellyel létrehoz egy ID2D1DeviceContext objektumot, amelyre az átfedési objektum is rajzolhat. IDWriteFactory::CreateTextFormat
GameInfoOverlay::CreateDeviceDependentResources
A GameInfoOverlay::CreateDeviceDependentResources a módszerünk az ecsetek létrehozására, amelyeket a szöveg rajzolásához fogunk használni. Ehhez beszerezünk egy ID2D1DeviceContext2 objektumot, amely lehetővé teszi a geometria létrehozását és rajzolását, valamint olyan funkciók használatát, mint a tinta és a gradiens háló megjelenítése. Ezután létrehozunk egy színes keféket az ID2D1SolidColorBrush használatával a következő felhasználói felületi elemek rajzolásához.
- Fekete ecset téglalapháttérhez
- Fehér ecset az állapotszöveghez
- Narancssárga ecset műveletszöveghez
DeviceResources::SetDpi
A DeviceResources::SetDpi metódus az ablak hüvelykenkénti pontjait állítja be. Ez a metódus a DPI módosításakor lesz meghívva, és újra kell módosítani, ami a játékablak átméretezésekor történik. A DPI frissítése után ez a metódus meghívjaDeviceResources::CreateWindowSizeDependentResources, hogy a szükséges erőforrások minden alkalommal újra létre legyenek hozva az ablak átméretezésekor.
GameInfoOverlay::CreateWindowsSizeDependentResources
A GameInfoOverlay::CreateWindowsSizeDependentResources metódusban történik minden rajzolás. Az alábbiakban a metódus lépéseinek vázlata található.
A rendszer három téglalapot hoz létre a Cím, Törzsés Művelet szöveg felhasználói felületének szövegének kimetszéséhez.
m_titleRectangle = D2D1::RectF( GameInfoOverlayConstant::SideMargin, GameInfoOverlayConstant::TopMargin, overlaySize.width - GameInfoOverlayConstant::SideMargin, GameInfoOverlayConstant::TopMargin + GameInfoOverlayConstant::TitleHeight ); m_actionRectangle = D2D1::RectF( GameInfoOverlayConstant::SideMargin, overlaySize.height - (GameInfoOverlayConstant::ActionHeight + GameInfoOverlayConstant::BottomMargin), overlaySize.width - GameInfoOverlayConstant::SideMargin, overlaySize.height - GameInfoOverlayConstant::BottomMargin ); m_bodyRectangle = D2D1::RectF( GameInfoOverlayConstant::SideMargin, m_titleRectangle.bottom + GameInfoOverlayConstant::Separator, overlaySize.width - GameInfoOverlayConstant::SideMargin, m_actionRectangle.top - GameInfoOverlayConstant::Separator );Létrejön egy
m_levelBitmapnevű bitkép, amely figyelembe veszi az aktuális DPI-t CreateBitmaphasználatával.m_levelBitmap2D renderelési céltárgyként van beállítva a ID2D1DeviceContext::SetTargetsegítségével.A bitkép törlődik, minden képpont feketévé válik ID2D1RenderTarget::Clear.
ID2D1RenderTarget::BeginDraw meghívására kerül sor a rajzolás megkezdéséhez.
A DrawText a
m_titleString,m_bodyStringésm_actionStringtárolt szöveg rajzolására hívható meg a megfelelő téglalapban, az ehhez tartozó ID2D1SolidColorBrush használatával.ID2D1RenderTarget::EndDraw az
m_levelBitmapösszes rajzműveletének leállításához hívják meg.Egy másik Bitkép létrehozása CreateBitmap
m_tooSmallBitmaphasználatával történik tartalékként való használatra, csak akkor jelenik meg, ha a megjelenítési konfiguráció túl kicsi a játékhoz.Ismételje meg a folyamatot a
m_levelBitmap-ra vonatkozóan am_tooSmallBitmapesetében, ezúttal csak a sztringPaused-t helyezze el a törzsben.
Most már csak hat módszerre van szükségünk, hogy kitöltsük a hat átfedési állapotunk szövegét!
A játék állapotának jelképe
A játékban szereplő hat átfedési állapot mindegyikének van egy megfelelő metódusa a GameInfoOverlay objektumban. Ezek a módszerek a átfedés egy változatát rajzolják meg, hogy explicit információkat közöljenek a játékossal magáról a játékról. Ez a kommunikáció egy Cím és egy Törzs szöveggel van ábrázolva. Mivel a minta már beállította az adatok erőforrásait és elrendezését az inicializáláskor és a GameInfoOverlay::CreateDeviceDependentResources metódussal, csak az átfedéshelyzet-specifikus szövegeket kell megadnia.
Az átfedés állapotának része az alábbi módszerek egyikének hívásával kerül beállításra.
| Játék állapota | Állapot beállító metódus | Állapotmezők |
|---|---|---|
| Betöltés | GameInfoOverlay::SetGameLoading |
Cím Erőforrások betöltése Törzs növekményesen nyomtatja a "." szöveget a tevékenység betöltéséhez. |
| Kezdeti kezdési/magas pontszámú statisztikák | GameInfoOverlay::SetGameStats |
cím magas pontszámú törzs befejezett szintek # Összes pont # Összes felvétel # |
| Szint kezdete | GameInfoOverlay::SetLevelStart |
Cím szint # Törzs szint cél leírása. |
| Játék szüneteltetve | GameInfoOverlay::SetPause |
Cím Játék szüneteltetve Törzs Nincs |
| Vége a játéknak | GameInfoOverlay::SetGameOver |
cím játék törzs befejezett szintek # Összes pont # Összes lövés # Befejezett szintek # Magas pontszám # |
| Játék nyert | GameInfoOverlay::SetGameOver |
cím NYERT! Teljes szintek # Összes pont # Összes felvétel # Befejezett szintek # Magas pontszám # |
A GameInfoOverlay::CreateWindowSizeDependentResources metódussal a minta három négyszögletes területet deklarált, amelyek az átfedés adott régióinak felelnek meg.
Ezeket a területeket szem előtt tartva nézzük meg az egyik állapotspecifikus metódust, GameInfoOverlay::SetGameStats, és nézzük meg, hogyan rajzolódik meg az átfedés.
void GameInfoOverlay::SetGameStats(int maxLevel, int hitCount, int shotCount)
{
int length;
auto d2dContext = m_deviceResources->GetD2DDeviceContext();
d2dContext->SetTarget(m_levelBitmap.get());
d2dContext->BeginDraw();
d2dContext->SetTransform(D2D1::Matrix3x2F::Identity());
d2dContext->FillRectangle(&m_titleRectangle, m_backgroundBrush.get());
d2dContext->FillRectangle(&m_bodyRectangle, m_backgroundBrush.get());
m_titleString = L"High Score";
d2dContext->DrawText(
m_titleString.c_str(),
m_titleString.size(),
m_textFormatTitle.get(),
m_titleRectangle,
m_textBrush.get()
);
length = swprintf_s(
wsbuffer,
bufferLength,
L"Levels Completed %d\nTotal Points %d\nTotal Shots %d",
maxLevel,
hitCount,
shotCount
);
m_bodyString = std::wstring(wsbuffer, length);
d2dContext->DrawText(
m_bodyString.c_str(),
m_bodyString.size(),
m_textFormatBody.get(),
m_bodyRectangle,
m_textBrush.get()
);
// We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
// is lost. It will be handled during the next call to Present.
HRESULT hr = d2dContext->EndDraw();
if (hr != D2DERR_RECREATE_TARGET)
{
// The D2DERR_RECREATE_TARGET indicates there has been a problem with the underlying
// D3D device. All subsequent rendering will be ignored until the device is recreated.
// This error will be propagated and the appropriate D3D error will be returned from the
// swapchain->Present(...) call. At that point, the sample will recreate the device
// and all associated resources. As a result, the D2DERR_RECREATE_TARGET doesn't
// need to be handled here.
winrt::check_hresult(hr);
}
}
A GameInfoOverlay objektum inicializált Direct2D eszközkörnyezetét használva ez a módszer fekete színnel tölti ki a cím és a törzs téglalapjait a háttérkefe használatával. A "High Score" sztring szövegét a cím téglalapjához rajzolja, és egy sztringet, amely a játék állapotadatait frissíti a szövegtörzs téglalapjához a fehér szöveges ecsettel.
A művelet téglalapja frissül a GameInfoOverlay::SetAction
Bármely adott állapot átfedését a GameMain::SetGameInfoOverlay metódus választja ki.
void GameMain::SetGameInfoOverlay(GameInfoOverlayState state)
{
m_gameInfoOverlayState = state;
switch (state)
{
case GameInfoOverlayState::Loading:
m_uiControl->SetGameLoading(m_loadingCount);
break;
case GameInfoOverlayState::GameStats:
m_uiControl->SetGameStats(
m_game->HighScore().levelCompleted + 1,
m_game->HighScore().totalHits,
m_game->HighScore().totalShots
);
break;
case GameInfoOverlayState::LevelStart:
m_uiControl->SetLevelStart(
m_game->LevelCompleted() + 1,
m_game->CurrentLevel()->Objective(),
m_game->CurrentLevel()->TimeLimit(),
m_game->BonusTime()
);
break;
case GameInfoOverlayState::GameOverCompleted:
m_uiControl->SetGameOver(
true,
m_game->LevelCompleted() + 1,
m_game->TotalHits(),
m_game->TotalShots(),
m_game->HighScore().totalHits
);
break;
case GameInfoOverlayState::GameOverExpired:
m_uiControl->SetGameOver(
false,
m_game->LevelCompleted(),
m_game->TotalHits(),
m_game->TotalShots(),
m_game->HighScore().totalHits
);
break;
case GameInfoOverlayState::Pause:
m_uiControl->SetPause(
m_game->LevelCompleted() + 1,
m_game->TotalHits(),
m_game->TotalShots(),
m_game->TimeRemaining()
);
break;
}
}
A játéknak van egy módja, hogy szöveges információkat közöljön a játékossal a játék állapota alapján, és az egész játék során át tudjuk váltani, hogy mi jelenjen meg számukra.
Következő lépések
A következő témakörben, Vezérlők hozzáadása, azt vizsgáljuk meg, hogyan kommunikál a játékos a mintajátékkal, és hogyan változik a bemenet a játék állapotában.