Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Catatan
Topik ini adalah bagian dari membuat permainan Platform Windows Universal sederhana (UWP) dengan seri tutorial DirectX. Topik di tautan tersebut mengatur konteks untuk seri.
Sekarang setelah game kami memiliki visual 3D-nya, saatnya untuk fokus pada penambahan beberapa elemen 2D sehingga game dapat memberikan umpan balik tentang status permainan kepada pemain. Ini dapat dicapai dengan menambahkan opsi menu sederhana dan komponen tampilan heads-up di atas output alur grafis 3-D.
Catatan
Jika Anda belum mengunduh kode game terbaru untuk sampel ini, buka game sampel Direct3D. Sampel ini adalah bagian dari koleksi besar sampel fitur UWP. Untuk petunjuk tentang cara mengunduh sampel, lihat Contoh aplikasi untuk pengembangan Windows.
Tujuan
Dengan menggunakan Direct2D, tambahkan sejumlah grafis dan perilaku antarmuka pengguna ke game DirectX UWP kami termasuk:
- Tampilan heads-up, termasuk persegi panjang batas pengontrol move-look
- Menu status permainan
Overlay antarmuka pengguna
Meskipun ada banyak cara untuk menampilkan teks dan elemen antarmuka pengguna dalam game DirectX, kita akan fokus menggunakan Direct2D. Kita juga akan menggunakan DirectWrite untuk elemen teks.
Direct2D adalah sekumpulan API gambar 2D yang digunakan untuk menggambar primitif dan efek berbasis piksel. Saat memulai dengan Direct2D, yang terbaik adalah menjaga semuanya tetap sederhana. Tata letak kompleks dan perilaku antarmuka membutuhkan waktu dan perencanaan. Jika game Anda memerlukan antarmuka pengguna yang kompleks, seperti yang ditemukan dalam simulasi dan game strategi, pertimbangkan untuk menggunakan XAML sebagai gantinya.
Catatan
Untuk informasi tentang mengembangkan antarmuka pengguna dengan XAML dalam game UWP DirectX, lihat Memperluas permainan sampel.
Direct2D tidak dirancang khusus untuk antarmuka pengguna atau tata letak seperti HTML dan XAML. Ini tidak menyediakan komponen antarmuka pengguna seperti daftar, kotak, atau tombol. Ini juga tidak menyediakan komponen tata letak seperti div, tabel, atau kisi.
Untuk permainan sampel ini, kami memiliki dua komponen UI utama.
- Tampilan heads-up untuk skor dan kontrol dalam game.
- Overlay yang digunakan untuk menampilkan teks dan opsi status permainan seperti menjeda info dan opsi mulai tingkat.
Menggunakan Direct2D untuk tampilan heads-up
Gambar berikut menunjukkan tampilan heads-up dalam game untuk sampel. Ini sederhana dan tidak berantakan, memungkinkan pemain untuk fokus menavigasi dunia 3D dan menembak target. Antarmuka atau tampilan head-up yang baik tidak boleh mempersulit kemampuan pemain untuk memproses dan bereaksi terhadap peristiwa dalam permainan.
Overlay terdiri dari primitif dasar berikut.
-
Teks DirectWrite di sudut kanan atas yang menginformasikan pemutar
- Temuan berhasil
- Jumlah bidikan yang dilakukan pemain
- Waktu tersisa di tingkat
- Nomor tingkat saat ini
- Dua segmen garis berpotret yang digunakan untuk membentuk rambut silang
- Dua persegi panjang di sudut bawah untuk batas kontrol gerak-lihat.
Status tampilan heads-up dalam game dari overlay digambar dalam metode GameHud::Render dari kelas GameHud. Dalam metode ini, overlay Direct2D yang mewakili UI kami diperbarui untuk mencerminkan perubahan jumlah temuan, sisa waktu, dan angka tingkat.
Jika permainan telah diinisialisasi, kami menambahkan TotalHits(), , dan TotalShots() ke TimeRemaining() dan menentukan format cetak. Kita kemudian dapat menggambarnya menggunakan metode DrawText. Kami melakukan hal yang sama untuk indikator tingkat saat ini, menggambar angka kosong untuk menunjukkan tingkat yang tidak lengkap seperti ➀, dan angka yang diisi seperti ➊ untuk menunjukkan bahwa tingkat tertentu telah selesai.
Cuplikan kode berikut berjalan melalui proses metode GameHud::Render untuk
- Membuat Bitmap menggunakan **ID2D1RenderTarget::D rawBitmap **
- Memisahkan area UI menjadi persegi panjang menggunakan D2D1::RectF
- Menggunakan DrawText untuk membuat elemen teks
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
...
}
}
}
Memecah metode lebih jauh, bagian metode GameHud::Render ini menarik persegi panjang pemindahan dan api kami dengan ID2D1RenderTarget::D rawRectangle, dan crosshair menggunakan dua panggilan ke ID2D1RenderTarget::D rawLine.
// 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
);
}
Dalam metode GameHud::Render kami menyimpan ukuran logis jendela game dalam windowBounds variabel . Ini menggunakan GetLogicalSize metode kelas DeviceResources .
auto windowBounds = m_deviceResources->GetLogicalSize();
Mendapatkan ukuran jendela permainan sangat penting untuk pemrograman UI. Ukuran jendela diberikan dalam pengukuran yang disebut DIP (piksel independen perangkat), di mana DIP didefinisikan sebagai 1/96 inci. Direct2D menskalakan unit gambar ke piksel aktual ketika gambar terjadi, melakukannya dengan menggunakan pengaturan Titik Windows per inci (DPI). Demikian pula, saat Anda menggambar teks menggunakan DirectWrite, Anda menentukan DIP daripada titik untuk ukuran font. DIP dinyatakan sebagai angka titik mengambang.
Menampilkan info status permainan
Selain tampilan heads-up, permainan sampel memiliki overlay yang mewakili enam status game. Semua status menampilkan primitif persegi panjang hitam besar dengan teks untuk dibaca pemutar. Persegi panjang dan crosshair pengontrol tampilan pemindahan tidak digambar karena tidak aktif di status ini.
Overlay dibuat menggunakan kelas GameInfoOverlay , memungkinkan kami untuk mengalihkan teks apa yang ditampilkan agar selaras dengan status permainan.
Overlay dipecah menjadi dua bagian: Status dan Tindakan. Bagian Status dipecah lebih lanjut menjadi judul dan persegi panjang Isi . Bagian Tindakan hanya memiliki satu persegi panjang. Setiap persegi panjang memiliki tujuan yang berbeda.
-
titleRectangleberisi teks judul. -
bodyRectangleberisi teks isi. -
actionRectangleberisi teks yang menginformasikan pemutar untuk mengambil tindakan tertentu.
Permainan ini memiliki enam status yang dapat diatur. Status permainan yang disampaikan menggunakan bagian Status dari overlay. Persegi status diperbarui menggunakan sejumlah metode yang sesuai dengan status berikut.
- Memuat
- Statistik mulai/skor tinggi awal
- Mulai tingkat
- Permainan dijeda
- Tamat
- Permainan yang dimenangkan
Bagian Tindakan dari overlay diperbarui menggunakan metode GameInfoOverlay::SetAction, memungkinkan teks tindakan diatur ke salah satu hal berikut.
- "Ketuk untuk memutar lagi..."
- "Pemuatan tingkat, harap tunggu ..."
- "Ketuk untuk melanjutkan ..."
- Tidak ada
Catatan
Kedua metode ini akan dibahas lebih lanjut di bagian Mewakili status permainan.
Tergantung pada apa yang terjadi dalam permainan, bidang teks bagian Status dan Tindakan disesuaikan. Mari kita lihat bagaimana kita menginisialisasi dan menggambar overlay untuk enam status ini.
Menginisialisasi dan menggambar overlay
Enam status Status memiliki beberapa hal yang sama, membuat sumber daya dan metode yang mereka butuhkan sangat mirip. - Mereka semua menggunakan persegi panjang hitam di tengah layar sebagai latar belakang mereka. - Teks yang ditampilkan adalah teks Judul atau Isi . - Teks menggunakan font Segoe UI dan digambar di atas persegi panjang belakang.
Permainan sampel memiliki empat metode yang mulai dimainkan saat membuat overlay.
GameInfoOverlay::GameInfoOverlay
Konstruktor GameInfoOverlay::GameInfoOverlay menginisialisasi overlay, mempertahankan permukaan bitmap yang akan kita gunakan untuk menampilkan info kepada pemutar. Konstruktor mendapatkan pabrik dari objek ID2D1Device yang diteruskan ke dalamnya, yang digunakannya untuk membuat ID2D1DeviceContext yang dapat digambar objek overlay itu sendiri. IDWriteFactory::CreateTextFormat
GameInfoOverlay::CreateDeviceDependentResources
GameInfoOverlay::CreateDeviceDependentResources adalah metode kami untuk membuat kuas yang akan digunakan untuk menggambar teks kita. Untuk melakukan ini, kami mendapatkan objek ID2D1DeviceContext2 yang memungkinkan pembuatan dan gambar geometri, ditambah fungsionalitas seperti tinta dan penyajian jala gradien. Kami kemudian membuat serangkaian kuas berwarna menggunakan ID2D1SolidColorBrush untuk menggambar elemen UI berikut.
- Kuas hitam untuk latar belakang persegi panjang
- Kuas putih untuk teks status
- Sikat oranye untuk teks tindakan
DeviceResources::SetDpi
Metode DeviceResources::SetDpi mengatur titik per inci jendela. Metode ini dipanggil ketika DPI diubah dan perlu dibaca yang terjadi ketika jendela game diubah ukurannya. Setelah memperbarui DPI, metode ini juga memanggilDeviceResources::CreateWindowSizeDependentResources untuk memastikan sumber daya yang diperlukan dibuat ulang setiap kali jendela diubah ukurannya.
GameInfoOverlay::CreateWindowsSizeDependentResources
Metode GameInfoOverlay::CreateWindowsSizeDependentResources adalah tempat semua gambar kami berlangsung. Berikut ini adalah kerangka langkah-langkah metode.
Tiga persegi panjang dibuat untuk memisahkan teks UI untuk teks Judul, Isi, dan Tindakan .
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 );Bitmap dibuat bernama
m_levelBitmap, memperhitungkan DPI saat ini menggunakan CreateBitmap.m_levelBitmapditetapkan sebagai target render 2D kami menggunakan ID2D1DeviceContext::SetTarget.Bitmap dibersihkan dengan setiap piksel yang dibuat hitam menggunakan ID2D1RenderTarget::Clear.
ID2D1RenderTarget::BeginDraw dipanggil untuk memulai menggambar.
DrawText dipanggil untuk menggambar teks yang disimpan di
m_titleString,m_bodyString, danm_actionStringdalam persegi yang sesuai menggunakan ID2D1SolidColorBrush yang sesuai.ID2D1RenderTarget::EndDraw dipanggil untuk menghentikan semua operasi menggambar pada
m_levelBitmap.Bitmap lain dibuat menggunakan CreateBitmap bernama
m_tooSmallBitmapuntuk digunakan sebagai fallback, menunjukkan hanya jika konfigurasi tampilan terlalu kecil untuk game.Ulangi proses untuk menggambar untuk
m_levelBitmapm_tooSmallBitmap, kali ini hanya menggambar stringPauseddalam isi.
Sekarang yang kita butuhkan adalah enam metode untuk mengisi teks dari enam status overlay kita!
Mewakili status permainan
Masing-masing dari enam status overlay dalam game memiliki metode yang sesuai dalam objek GameInfoOverlay . Metode ini menarik variasi overlay untuk mengomunikasikan info eksplisit kepada pemain tentang permainan itu sendiri. Komunikasi ini diwakili dengan string Judul dan Isi . Karena sampel sudah mengonfigurasi sumber daya dan tata letak untuk info ini ketika diinisialisasi dan dengan metode GameInfoOverlay::CreateDeviceDependentResources , hanya perlu menyediakan string khusus status overlay.
Bagian Status dari overlay diatur dengan panggilan ke salah satu metode berikut.
| Status permainan | Metode set status | Bidang status |
|---|---|---|
| Memuat | GameInfoOverlay::SetGameLoading |
Secara bertahap mencetak "." untuk menyiratkan aktivitas pemuatan. |
| Statistik mulai/skor tinggi awal | GameInfoOverlay::SetGameStats |
Title High Score Body Levels Completed # Total Point # Total Shots # |
| Mulai tingkat | GameInfoOverlay::SetLevelStart |
Deskripsi tujuan Tingkat Judul # Tingkat Isi . |
| Permainan dijeda | GameInfoOverlay::SetPause |
Permainan Judul Dijeda Isi Tidak Ada |
| Tamat | GameInfoOverlay::SetGameOver |
Title Game Over Body Levels Completed # Total Point # Total Shots # Levels Completed # High Score # |
| Permainan yang dimenangkan | GameInfoOverlay::SetGameOver |
Judul Anda WON! Tingkat Isi Selesai # Total Poin # Total Tembakan # Level Selesai # Skor Tinggi # |
Dengan metode GameInfoOverlay::CreateWindowSizeDependentResources, sampel mendeklarasikan tiga area persegi panjang yang sesuai dengan wilayah overlay tertentu.
Dengan mengingat area ini, mari kita lihat salah satu metode khusus status, GameInfoOverlay::SetGameStats, dan lihat bagaimana overlay digambar.
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);
}
}
Menggunakan konteks perangkat Direct2D yang diinisialisasi objek GameInfoOverlay , metode ini mengisi judul dan persegi panjang tubuh dengan warna hitam menggunakan kuas latar belakang. Ini menggambar teks untuk string "Skor Tinggi" ke judul persegi panjang dan string yang berisi informasi status permainan pembaruan ke persegi panjang tubuh menggunakan kuas teks putih.
Persegi panjang tindakan diperbarui oleh panggilan berikutnya ke GameInfoOverlay::SetAction dari metode pada objek GameMain , yang menyediakan info status game yang diperlukan oleh GameInfoOverlay::SetAction untuk menentukan pesan yang tepat kepada pemain, seperti "Ketuk untuk melanjutkan".
Overlay untuk status tertentu dipilih dalam metode GameMain::SetGameInfoOverlay seperti ini:
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;
}
}
Sekarang permainan ini memiliki cara untuk mengomunikasikan info teks kepada pemain berdasarkan status permainan, dan kami memiliki cara untuk mengalihkan apa yang ditampilkan kepada mereka di seluruh permainan.
Langkah berikutnya
Di topik berikutnya, Menambahkan kontrol, kita melihat bagaimana pemain berinteraksi dengan permainan sampel, dan bagaimana input mengubah status game.