Aracılığıyla paylaş


Kullanıcı arabirimi ekleme

Uyarı

Bu konu, DirectX öğretici serisi ile basit bir Evrensel Windows Platformu (UWP) oluşturma oyunun bir parçasıdır. Bu bağlantıdaki konu başlığı, serinin bağlamını ayarlar.

Artık oyunumuzun 3B görselleri olduğuna göre, oyunun oyuncuya oyun durumu hakkında geri bildirim sağlayabilmesi için bazı 2B öğeler eklemeye odaklanmanın zamanı geldi. Bu, 3B grafik işlem hattı çıkışının üzerine basit menü seçenekleri ve başlık görüntüleme bileşenleri eklenerek gerçekleştirilebilir.

Uyarı

Bu örnek için en son oyun kodunu indirmediyseniz Direct3D örnek oyunbölümüne gidin. Bu örnek, büyük bir UWP özellik örnekleri koleksiyonunun bir parçasıdır. Örneği indirme talimatları için, Windows geliştirme için örnek uygulamalar sayfasına bakın.

Amaç

Direct2D kullanarak UWP DirectX oyunumuza aşağıdakiler dahil olmak üzere bir dizi kullanıcı arabirimi grafiği ve davranışı ekleyin:

Kullanıcı arabirimi katmanı

Bir DirectX oyununda metin ve kullanıcı arabirimi öğelerini görüntülemenin birçok yolu olsa da, Direct2Dkullanmaya odaklanacağız. Metin öğeleri için DirectWrite de kullanacağız.

Direct2D, piksel tabanlı temel öğeler ve efektler çizmek için kullanılan bir 2B çizim API'leri kümesidir. Direct2D ile çalışmaya başlarken, işleri basit tutmak en iyisidir. Karmaşık düzenlerin ve arabirim davranışlarının zamana ve planlamaya ihtiyacı vardır. Oyununuz simülasyon ve strateji oyunlarında bulunanlar gibi karmaşık bir kullanıcı arabirimi gerektiriyorsa bunun yerine XAML kullanmayı göz önünde bulundurun.

Uyarı

UWP DirectX oyununda XAML ile kullanıcı arabirimi geliştirme hakkında bilgi için bkz. Örnek oyunu genişletme.

Direct2D, html ve XAML gibi kullanıcı arabirimleri veya düzenler için özel olarak tasarlanmamıştır. Listeler, kutular veya düğmeler gibi kullanıcı arabirimi bileşenleri sağlamaz. Ayrıca divs, tablolar veya kılavuzlar gibi düzen bileşenleri sağlamaz.

Bu örnek oyun için iki ana kullanıcı arabirimi bileşenimiz var.

  1. Puan ve oyun içi kontroller için bir uyarı ekranı.
  2. Duraklatma bilgileri ve düzey başlangıç seçenekleri gibi oyun durumu metinlerini ve seçeneklerini görüntülemek için kullanılan katman.

Uyarı ekranı için Direct2D kullanma

Aşağıdaki görüntüde örnek için oyun içi uyarı ekranı gösterilmektedir. Bu, basit ve sade bir yapı sunarak oyuncunun 3B dünyada gezinmeye ve hedefleri vurmaya odaklanmasına olanak tanır. İyi bir arayüz veya uyarı ekranı, oyuncunun oyundaki olayları işleme ve tepki verme becerisini asla karmaşık hale getirmek zorunda değildir.

bir oyun katmanı ekran görüntüsü

Katman, aşağıdaki temel ilkel nesnelerden oluşur.

Katmanın oyun içi uyarı görüntüleme durumu, GameHud sınıfının GameHud::Render yönteminde çizilir. Bu yöntemde, kullanıcı arabirimimizi temsil eden Direct2D katman, isabet sayısı, kalan süre ve düzey sayısındaki değişiklikleri yansıtacak şekilde güncelleştirilir.

Oyun başlatıldıysa, TotalHits() arabelleğe TotalShots(), TimeRemaining()ve ekleyip yazdırma biçimini belirtiriz. Daha sonra DrawText yöntemini kullanarak çizebiliriz. Aynı işlemi geçerli düzey göstergesi için de yaparız, ➀ gibi tamamlanmamış düzeyleri ve belirli bir düzeyin tamamlandığını göstermek için ➊ gibi doldurulmuş sayıları göstermek için boş sayılar çizeriz.

Aşağıdaki kod parçacığı, GameHud::Render yönteminin işlemini göstermektedir

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
            ...
        }
    }
}

Yöntemi daha da açarak, GameHud::Render yönteminin bu parçası, ID2D1RenderTarget::DrawRectanglekullanarak hareket ve ateş dikdörtgenlerimizi ve iki çağrı ile ID2D1RenderTarget::DrawLinekullanarak nişangahları çizer.

// 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
        );
}

GameHud::Render yönteminde oyun penceresinin mantıksal boyutunu windowBounds değişkeninde depolarız. Bu, GetLogicalSize sınıfının yöntemini kullanır.

auto windowBounds = m_deviceResources->GetLogicalSize();

Kullanıcı arabirimi programlama için oyun penceresinin boyutunu elde etmek çok önemlidir. Pencerenin boyutu, bir DIP'in bir inçin 1/96'sı olarak tanımlandığı DIPs (cihazdan bağımsız pikseller) adlı bir ölçü biriminde verilir. Direct2D, çizim gerçekleştiğinde çizim birimlerini gerçek piksellere ölçeklendirir ve bunu yapmak için Windows inç başına nokta (DPI) ayarını kullanır. Aynı şekilde, DirectWritekullanarak metin çizdiğinizde, yazı tipinin boyutu için nokta yerine DIP'ler belirtirsiniz. DIP'ler kayan nokta sayıları olarak ifade edilir. 

Oyun durumu bilgilerini görüntüleme

Uyarı ekranının yanı sıra, örnek oyun altı oyun durumunu temsil eden bir katmana sahiptir. Tüm durumlar, oyuncunun okuması için metin içeren büyük siyah bir dikdörtgen şeklinde arka plan içerir. Hareket-bakış denetleyicisi dikdörtgenleri ve nişangahlar, bu durumlarda etkin olmadıkları için çizilmez.

Katman, GameInfoOverlay sınıfı kullanılarak oluşturulur ve oyunun durumuyla uyumlu olacak şekilde hangi metnin görüntüleneceğini değiştirmemize olanak sağlar.

Katman durumunu ve eylemini

Katman iki bölüme ayrılır: Durum ve Eylem. Durum bölümü, Başlık ve Gövde dikdörtgenlerine daha ayrıntılı olarak ayrılmıştır. Eylemi bölümünde yalnızca bir dikdörtgen vardır. Her dikdörtgenin farklı bir amacı vardır.

  • titleRectangle başlık metnini içerir.
  • bodyRectangle gövde metnini içerir.
  • actionRectangle, oyuncuyu belirli bir eylemi gerçekleştirmesi konusunda bilgilendiren metni içerir.

Oyun, ayarlanabilen altı eyalete sahiptir. Oyunun durumu, katmanın Durumu bölümü kullanılarak iletilir. Durumu dikdörtgenleri, aşağıdaki durumlara karşılık gelen bir dizi yöntem kullanılarak güncellenir.

  • Yükleniyor
  • İlk başlangıç/yüksek puan istatistikleri
  • Düzey başlangıç
  • Oyun duraklatıldı
  • Oyun Bitti
  • Oyun kazandı

Katman Eylem bölümü, GameInfoOverlay::SetAction yöntemi kullanılarak güncellenir, bu, eylem metninin aşağıdakilerden birine ayarlanmasına olanak tanır.

  • "Yeniden oynatmak için dokunun..."
  • Seviye yükleniyor, lütfen bekleyin ...
  • "Devam etmek için dokunun ..."
  • Hiç kimse

Uyarı

Bu yöntemlerin her ikisi de Oyun durumunu temsil etme bölümünde daha ayrıntılı olarak ele alınacaktır.

Oyunda neler olduğuna bağlı olarak, Durum ve Eylem bölüm metin alanları ayarlanır. Şimdi bu altı durum için katmanı nasıl başlatıp çizdiğimize bakalım.

Bindirmeyi başlatma ve çizme

Altı Durum durumunun birkaç ortak yanı vardır ve bu da ihtiyaç duydukları kaynakları ve yöntemleri çok benzer hale getirir. - Hepsi arka plan olarak ekranın ortasında siyah bir dikdörtgen kullanır. - Görüntülenen metin Başlık veya Gövde metnidir. - Metin Segoe UI yazı tipini kullanır ve arka dikdörtgenin üzerine çizilir.

Örnek oyunda katman oluşturulurken devreye giren dört yöntem vardır.

GameBilgisiÜzeri::GameBilgisiÜzeri

GameInfoOverlay::GameInfoOverlay oluşturucu, katmanı başlatır ve oyuncuya bilgi görüntülemek için kullanacağımız bit eşlem yüzeyini sürdürür. Oluşturucu, kendisine geçirilen ID2D1Device nesnesinden bir fabrika alır ve bu, katman nesnesinin çizim yapabileceği bir ID2D1DeviceContext oluşturmak için kullanılır. IDWriteFactory::CreateTextFormat

GameInfoOverlay::CreateDeviceDependentResources

GameInfoOverlay::CreateDeviceDependentResources, metnimizi çizmek için kullanılacak fırçaları oluşturma yöntemimizdir. Bunu yapmak için, geometrinin oluşturulmasını ve çizimini sağlayan bir ID2D1DeviceContext2 nesnesinin yanı sıra mürekkep ve gradyan mesh işleme gibi işlevler elde ediyoruz. Ardından, aşağıdaki kullanıcı arabirimi öğelerini çizmek için ID2D1SolidColorBrush kullanarak bir dizi renkli fırça oluştururuz.

  • Dikdörtgen arka planları için siyah fırça
  • Durum metni için beyaz fırça
  • Eylem metni için turuncu fırça

DeviceResources::SetDpi

DeviceResources::SetDpi yöntemi pencerenin inç başına nokta sayısını ayarlar. DPI değiştirildiğinde ve bu nedenle yeniden ayarlanması gerektiğinde bu yöntem çağrılır; bu durum oyun penceresi yeniden boyutlandırıldığında meydana gelir. DPI güncelleştirildikten sonra, bu yöntemDeviceResources::CreateWindowSizeDependentResources çağırarak pencerenin her yeniden boyutlandırıldığında gerekli kaynakların yeniden oluşturulmasını sağlar.

GameInfoOverlay::CreateWindowsSizeDependentResources

GameInfoOverlay::CreateWindowsSizeDependentResources yöntemi, tüm çizimimizin gerçekleştiği yerdir. Aşağıda yöntemin adımlarının bir ana hattı yer alır.

  • Başlık, Metinve Eylem metnini kullanıcı arabirimi metninden ayırmak için üç dikdörtgen oluşturulur.

    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
        );
    
  • m_levelBitmapadlı bir Bit Eşlem, geçerli DPI dikkate alınarak, CreateBitmapkullanılarak oluşturulur.

  • m_levelBitmap, ID2D1DeviceContext::SetTargetkullanılarak 2D işleme hedefimiz olarak ayarlanır.

  • Bit Eşlem, her piksel siyah yapılarak ID2D1RenderTarget::Clearkullanılarak temizlenir.

  • ID2D1RenderTarget::BeginDraw, çizimi başlatmak için çağrılır.

  • DrawText, karşılık gelen m_titleString kullanarak , m_bodyStringve m_actionString içinde depolanan metni uygun dikdörtgende çizmek için çağrılır.

  • ID2D1RenderTarget::EndDraw, m_levelBitmapüzerindeki tüm çizim işlemlerini durdurmak için çağrılır.

  • Geri dönüş olarak kullanmak üzere adlı m_tooSmallBitmap kullanılarak başka bir Bit Eşlem oluşturulur ve yalnızca görüntü yapılandırmasının oyun için çok küçük olup olmadığını gösterir.

  • m_levelBitmapiçin m_tooSmallBitmap üzerinde çizim işlemini yineleyin, bu kez yalnızca Paused dizesini gövdede çizin.

Şimdi tek ihtiyacımız olan altı katman durumunun metnini doldurmak için altı yöntem!

Oyun durumunu temsil etme

Oyundaki altı katman durumunun her biri, GameInfoOverlay nesnesinde karşılık gelen bir yönteme sahiptir. Bu yöntemler, oyuncuya oyunun kendisi hakkında açık bilgiler iletmek için katmanın bir varyasyonunu çizer. Bu iletişim, Başlık ve Gövde dizesiyle temsil edilir. Örnek, başlatıldığında ve GameInfoOverlay::CreateDeviceDependentResources yöntemiyle bu bilgi için kaynakları ve düzeni zaten yapılandırdığından, yalnızca katman durumuna özgü dizeleri sağlaması gerekir.

Katmanın Durumu bölümü, aşağıdaki yöntemlerden birine yapılan bir çağrıyla ayarlanır.

Oyun durumu Durum belirleme yöntemi Durum alanları
Yükleniyor GameInfoOverlay::SetGameLoading Başlık
Kaynakları Yükleme
Gövde
Yüklenme etkinliğini ima etmek için "." karakteri art arda yazdırılır.
İlk başlangıç/yüksek puan istatistikleri GameInfoOverlay::SetGameStats Başlık
Yüksek Puan
Bölüm
Tamamlanan Düzeyler #
Toplam Puan #
Toplam Atışlar #
Düzey başlangıç GameInfoOverlay::SetLevelStart Başlık
Düzeyi #
Gövde
Düzeyi hedef açıklaması.
Oyun duraklatıldı GameInfoOverlay::SetPause Başlık
Oyun Duraklatıldı
Gövde
Yok
Oyun Bitti GameInfoOverlay::SetGameOver Başlık
Oyun Bitti
Body
Tamamlanan Düzeyler
Toplam Puan
Toplam Atışlar
Tamamlanan Düzeyler
Yüksek Skor #
Oyun kazandı GameInfoOverlay::SetGameOver Başlık
Kazandınız!
İçerik
Düzey Tamamlandı #
Toplam Puan #
Toplam Atış #
Düzey Tamamlandı #
Yüksek Puan #

GameInfoOverlay::CreateWindowSizeDependentResources yöntemiyle, örnekte katmanın belirli bölgelerine karşılık gelen üç dikdörtgen alan bildirilmiştir.

Bu alanları göz önünde bulundurarak, duruma özgü yöntemlerden biri olan GameInfoOverlay::SetGameStatsyöntemine ve katmanın nasıl çizildiğine göz atalım.

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);
    }
}

GameInfoOverlay nesnesinin başlatıldığı Direct2D cihaz bağlamını kullanan bu yöntem, arka plan fırçasını kullanarak başlığı ve gövde dikdörtgenlerini siyahla doldurur. "Yüksek Puan" metnini beyaz metin fırçasını kullanarak başlık dikdörtgenine çizer ve oyun durumu bilgilerini içeren bir metni beyaz metin fırçasını kullanarak gövde dikdörtgenine çizer.

Eylem dikdörtgeni, GameMain nesnesi üzerindeki bir yöntemden GameInfoOverlay::SetAction'e yapılan sonraki bir çağrıyla güncellenir. Bu, GameInfoOverlay::SetAction tarafından oyuncuya "Devam etmek için dokunun" gibi doğru mesajı belirlemek için gereken oyun durumu bilgilerini sağlar.

Herhangi bir durum için katman, aşağıdaki gibi GameMain::SetGameInfoOverlay yönteminde seçilir:

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;
    }
}

Artık oyun, oyun durumuna göre oyuncuya metin bilgilerini iletmenin bir yoluna sahiptir ve oyun boyunca görüntülenenleri oyuncuya değiştirmenin bir yolu vardır.

Sonraki Adımlar

Bir sonraki konu başlığında, denetim ekleme, oyuncunun örnek oyunla nasıl etkileşime geçtiğini ve girişin oyun durumunu nasıl değiştirdiğini inceleyeceğiz.