Aracılığıyla paylaş


Oyunlar için giriş pratikleri

Bu konuda, Evrensel Windows Platformu (UWP) oyunlarında giriş cihazlarını etkili bir şekilde kullanmaya yönelik desenler ve teknikler açıklanmaktadır.

Bu konuyu okuyarak şunları öğreneceksiniz:

  • oyuncuları nasıl takip edeceğiniz ve şu anda hangi giriş ve gezinti cihazlarını kullandıkları
  • düğme geçişlerini algılama (basılıdan bırakılmaya, bırakmadan basılıya)
  • tek bir testle karmaşık düğme düzenlemelerini algılama

Giriş cihazı sınıfı seçme

ArcadeStick, FlightStickve Gamepadgibi birçok farklı giriş API'leri vardır. Oyununuz için hangi API'yi kullanacağınıza nasıl karar verirsiniz?

Oyununuz için size en uygun girişi hangi API'nin verdiğini seçmelisiniz. Örneğin, bir 2B platform oyunu yapıyorsanız, muhtemelen Gamepad sınıfını kullanabilir ve diğer sınıflar aracılığıyla sağlanan ek işlevlerle uğraşmayabilirsiniz. Bu, oyunu yalnızca oyunpad'lerini desteklemeye kısıtlar ve ek koda gerek duymadan birçok farklı oyun çubuğunda çalışacak tutarlı bir arabirim sağlar.

Öte yandan, karmaşık uçuş ve yarış simülasyonları için, tek bir oyuncu tarafından hala kullanılan ayrı pedallar veya kısıtlama gibi cihazlar da dahil olmak üzere meraklı oyuncuların sahip olabileceği niş cihazları desteklediklerinden emin olmak için tüm RawGameController nesnelerini temel olarak listelemek isteyebilirsiniz.

Buradan, bir giriş sınıfının FromGameController yöntemini (Gamepad.FromGameControllergibi) kullanarak her cihazın daha seçilmiş bir görünümü olup olmadığını görebilirsiniz. Örneğin, cihaz aynı zamanda bir Gamepadise, düğme eşleme kullanıcı arabirimini bunu yansıtacak şekilde ayarlamak ve aralarından seçim yapabileceğiniz bazı mantıklı varsayılan düğme eşlemeleri sağlamak isteyebilirsiniz. (Bu, yalnızca RawGameControllerkullanıyorsanız oyuncunun oyun çubuğu girişlerini el ile yapılandırmasını gerektirmenin aksinedir.)

Alternatif olarak, bir RawGameController (sırasıyla HardwareVendorId ve HardwareProductIdkullanarak) satıcı kimliğine (VID) ve ürün kimliğine (PID) bakabilir ve oyuncu tarafından el ile yapılan eşlemelerle gelecekte ortaya çıkan bilinmeyen cihazlarla uyumlu kalırken popüler cihazlar için önerilen düğme eşlemeleri sağlayabilirsiniz.

Bağlı denetleyicileri izleme

Her denetleyici türü bağlı denetleyicilerin bir listesini (Gamepad.Gamepadsgibi) içerirken, kendi oyun kumandaları listenizi korumak iyi bir fikirdir. Daha fazla bilgi için bkz. Gamepads listesi (her denetleyici türünün kendi konusunda benzer şekilde adlandırılmış bir bölümü vardır).

Ancak, oynatıcı oyun kumandasını çıkardığında veya yenisini taktığında ne olur? Bu olayları işlemeniz ve listenizi uygun şekilde güncelleştirmeniz gerekir. Daha fazla bilgi için Gamepad ekleme ve kaldırma bölümüne bakın (yine her denetleyici türünün kendi konusuna ait benzer isimli bir bölümü vardır).

Eklenen ve kaldırılan olaylar zaman uyumsuz olarak tetiklendiğinden, denetleyici listenizle ilgilenirken yanlış sonuçlar alabilirsiniz. Bu nedenle, denetleyici listenize her erişişinizde, aynı anda yalnızca bir iş parçacığının erişebilmesi için bu listenin çevresine bir kilit koymanız gerekir. Bu, Eşzamanlılık Çalışma Zamanı, özellikle critical_section sınıfıve <ppl.h>dosyasıyla yapılabilir.

Dikkate alınması gereken bir diğer şey de, bağlı denetleyiciler listesinin başlangıçta boş olması ve doldurulmak için bir veya iki saniye geçmesidir. Bu nedenle, başlangıç yönteminde yalnızca geçerli gamepad'i atarsanız, nullolur!

Bunu düzeltmek için, ana oyun kumandasını "yenileyen" bir yönteminiz olmalıdır (tek oyunculu bir oyunda; çok oyunculu oyunlar daha gelişmiş çözümler gerektirir). Ardından bu yöntemi hem denetleyicinize eklenen hem de kaldırılan olay işleyicilerinde veya güncelleştirme yönteminizde çağırmalısınız.

Aşağıdaki yöntem yalnızca listedeki ilk gamepad'i döndürür (veya liste boşsa nullptr ). Ardından, denetleyiciyle herhangi bir şey yaptığınız her zaman nullptr kontrol etmeyi hatırlamanız yeterlidir. Oyun kumandası bağlı olmadığında (örneğin, oyunu duraklatarak) oyun oynanmasını engellemek mi, yoksa girişi yok sayarak oyun oynamaya devam etmek mi istediğiniz size bağlıdır.

#include <ppl.h>

using namespace Platform::Collections;
using namespace Windows::Gaming::Input;
using namespace concurrency;

Vector<Gamepad^>^ m_myGamepads = ref new Vector<Gamepad^>();

Gamepad^ GetFirstGamepad()
{
    Gamepad^ gamepad = nullptr;
    critical_section::scoped_lock{ m_lock };

    if (m_myGamepads->Size > 0)
    {
        gamepad = m_myGamepads->GetAt(0);
    }

    return gamepad;
}

Hepsini bir araya getirmek için, bir oyun çubuğundan gelen girişleri işlemeye yönelik bir örnek aşağıda verilmiştir:

#include <algorithm>
#include <ppl.h>

using namespace Platform::Collections;
using namespace Windows::Foundation;
using namespace Windows::Gaming::Input;
using namespace concurrency;

static Vector<Gamepad^>^ m_myGamepads = ref new Vector<Gamepad^>();
static Gamepad^          m_gamepad = nullptr;
static critical_section  m_lock{};

void Start()
{
    // Register for gamepad added and removed events.
    Gamepad::GamepadAdded += ref new EventHandler<Gamepad^>(&OnGamepadAdded);
    Gamepad::GamepadRemoved += ref new EventHandler<Gamepad^>(&OnGamepadRemoved);

    // Add connected gamepads to m_myGamepads.
    for (auto gamepad : Gamepad::Gamepads)
    {
        OnGamepadAdded(nullptr, gamepad);
    }
}

void Update()
{
    // Update the current gamepad if necessary.
    if (m_gamepad == nullptr)
    {
        auto gamepad = GetFirstGamepad();

        if (m_gamepad != gamepad)
        {
            m_gamepad = gamepad;
        }
    }

    if (m_gamepad != nullptr)
    {
        // Gather gamepad reading.
    }
}

// Get the first gamepad in the list.
Gamepad^ GetFirstGamepad()
{
    Gamepad^ gamepad = nullptr;
    critical_section::scoped_lock{ m_lock };

    if (m_myGamepads->Size > 0)
    {
        gamepad = m_myGamepads->GetAt(0);
    }

    return gamepad;
}

void OnGamepadAdded(Platform::Object^ sender, Gamepad^ args)
{
    // Check if the just-added gamepad is already in m_myGamepads; if it isn't, 
    // add it.
    critical_section::scoped_lock lock{ m_lock };
    auto it = std::find(begin(m_myGamepads), end(m_myGamepads), args);

    if (it == end(m_myGamepads))
    {
        m_myGamepads->Append(args);
    }
}

void OnGamepadRemoved(Platform::Object^ sender, Gamepad^ args)
{
    // Remove the gamepad that was just disconnected from m_myGamepads.
    unsigned int indexRemoved;
    critical_section::scoped_lock lock{ m_lock };

    if (m_myGamepads->IndexOf(args, &indexRemoved))
    {
        if (m_gamepad == m_myGamepads->GetAt(indexRemoved))
        {
            m_gamepad = nullptr;
        }

        m_myGamepads->RemoveAt(indexRemoved);
    }
}

Kullanıcıları ve cihazlarını izleme

Tüm giriş cihazları bir Kullanıcı ile ilişkilendirilir, böylece kimlikleri oyunlarına, başarılarına, ayar değişikliklerine ve diğer etkinliklere bağlanabilir. Kullanıcılar oturum açabilir veya oturumunu istediğiniz zaman kapatabilir ve önceki kullanıcı oturumunu kapattıktan sonra sisteme bağlı kalan bir giriş cihazında farklı bir kullanıcının oturum açması yaygın olarak görülür. Kullanıcı oturum açtığında veya oturumu kapattığında, IGameController.UserChanged olayı oluşturulur. Oyuncuları ve kullandıkları cihazları izlemek için bu olay için bir olay işleyicisi kaydedebilirsiniz.

Bir giriş cihazının, ilgili kullanıcı arabirimi gezinti denetleyicisiile ilişkisinin bir yolu da kullanıcı kimliğidir.

Bu nedenlerle, oynatıcı girişi cihaz sınıfının User özelliğiyle ilişkilendirilmelidir (IGameController arabiriminden devralınır).

GitHubdaki UserGamepadPairingUWP örnek uygulaması, kullanıcıları ve kullandıkları cihazları nasıl izleyebilebileceğinizi gösterir.

Düğme geçişlerini algılama

Bazen bir düğmeye ilk ne zaman basıldığını veya serbest bırakıldığını bilmek istersiniz; yani, tam olarak düğme durumu serbest bırakıldığından basılana veya basılandan serbest bırakılmışa geçtiğinde. Bunu belirlemek için, önceki cihaz okumasını hatırlamanız ve nelerin değiştiğini görmek için geçerli okumayı bununla karşılaştırmanız gerekir.

Aşağıdaki örnekte, önceki okumanın anımsanması için temel bir yaklaşım gösterilmektedir; gamepad'ler burada gösterilmiştir, ancak ilkeler arcade çubuğu, yarış tekerleği ve diğer giriş cihazı türleri için aynıdır.

Gamepad gamepad;
GamepadReading newReading();
GamepadReading oldReading();

// Called at the start of the game.
void Game::Start()
{
    gamepad = Gamepad::Gamepads[0];
}

// Game::Loop represents one iteration of a typical game loop
void Game::Loop()
{
    // move previous newReading into oldReading before getting next newReading
    oldReading = newReading, newReading = gamepad.GetCurrentReading();

    // process device readings using buttonJustPressed/buttonJustReleased (see below)
}

Başka bir şey yapmadan önce, Game::Loop mevcut newReading değerini (önceki döngü yinelemesinden okunan gamepad) oldReading'ye taşır ve ardından bu döngü yinelemesi için newReading'ü yeni bir gamepad okumasıyla doldurur. Bu, düğme geçişlerini algılamak için ihtiyacınız olan bilgileri sağlar.

Aşağıdaki örnekte düğme geçişlerini algılamaya yönelik temel bir yaklaşım gösterilmektedir:

bool ButtonJustPressed(const GamepadButtons selection)
{
    bool newSelectionPressed = (selection == (newReading.Buttons & selection));
    bool oldSelectionPressed = (selection == (oldReading.Buttons & selection));

    return newSelectionPressed && !oldSelectionPressed;
}

bool ButtonJustReleased(GamepadButtons selection)
{
    bool newSelectionReleased =
        (GamepadButtons.None == (newReading.Buttons & selection));

    bool oldSelectionReleased =
        (GamepadButtons.None == (oldReading.Buttons & selection));

    return newSelectionReleased && !oldSelectionReleased;
}

Bu iki işlev önce düğme seçiminin Boole durumunu newReading ve oldReadingtüretir, ardından hedef geçişin gerçekleşip gerçekleşmediğini belirlemek için Boole mantığı gerçekleştirir. Bu işlevler true yalnızca yeni okuma hedef durumu içeriyorsa (sırasıyla basıldığında veya serbest bırakıldığında) ve eski okuma da hedef durumu içermiyorsa döndürür; aksi takdirde, falsedöndürür.

Karmaşık düğme düzenlemelerini algılama

Giriş cihazının her düğmesi, basıldığını (aşağı) veya yayınlandığını (yukarı) belirten bir dijital okuma sağlar. Verimlilik için düğme okumaları tek tek boole değerleri olarak temsil değildir; bunun yerine, hepsi GamepadButtonsgibi cihaza özgü numaralandırmalarla temsil edilen bitalanlarına paketlenir. Belirli düğmeleri okumak için, ilgilendiğiniz değerleri yalıtmak için bit düzeyinde maskeleme kullanılır. İlgili bit ayarlandığında düğmeye (aşağı) basılır; aksi takdirde serbest bırakılır (yukarı).

Tek düğmelerin basıldığı veya serbest bırakıldığı belirlendiğini hatırlayın; gamepad'ler burada gösterilmiştir, ancak ilkeler arcade çubuğu, yarış tekerleği ve diğer giriş cihazı türleri için aynıdır.

GamepadReading reading = gamepad.GetCurrentReading();

// Determines whether gamepad button A is pressed.
if (GamepadButtons::A == (reading.Buttons & GamepadButtons::A))
{
    // The A button is pressed.
}

// Determines whether gamepad button A is released.
if (GamepadButtons::None == (reading.Buttons & GamepadButtons::A))
{
    // The A button is released (not pressed).
}

Gördüğünüz gibi, tek bir düğmenin durumunu belirlemek kolaydır, ancak bazen birden çok düğmeye basılıp basılmadığını veya bırakılıp bırakılmadığını ya da belirli bir şekilde düzenlenip düzenlenmediğini belirlemek isteyebilirsiniz; yani bazıları basılı, bazıları değil. Birden çok düğmeyi test etmek, özellikle de karma düğme durumu olasılığıyla tek düğmeleri test etmekten daha karmaşıktır, ancak bu testler için tek ve birden çok düğme testi için de geçerli olan basit bir formül vardır.

Aşağıdaki örnek, A ve B oyun çubuğu düğmelerinin her ikisinin de basılıp basılmadığını belirler:

if ((GamepadButtons::A | GamepadButtons::B) == (reading.Buttons & (GamepadButtons::A | GamepadButtons::B))
{
    // The A and B buttons are both pressed.
}

Aşağıdaki örnek, A ve B oyun çubuğu düğmelerinin her ikisinin de yayınlanıp yayımlanmadığını belirler:

if ((GamepadButtons::None == (reading.Buttons & GamepadButtons::A | GamepadButtons::B))
{
    // The A and B buttons are both released (not pressed).
}

Aşağıdaki örnek, B düğmesi yayınlanırken A oyun çubuğu düğmesine basılıp basılmayacağını belirler:

if (GamepadButtons::A == (reading.Buttons & (GamepadButtons::A | GamepadButtons::B))
{
    // The A button is pressed and the B button is released (B is not pressed).
}

Bu örneklerin beşinin de ortak olduğu formül, test edilecek düğmelerin düzeninin eşitlik işlecinin sol tarafındaki ifade tarafından belirtilmesi, dikkate alınması gereken düğmelerin ise sağ taraftaki maskeleme ifadesi tarafından seçilmesidir.

Aşağıdaki örnek, önceki örneği yeniden yazarak bu formülü daha net bir şekilde gösterir:

auto buttonArrangement = GamepadButtons::A;
auto buttonSelection = (reading.Buttons & (GamepadButtons::A | GamepadButtons::B));

if (buttonArrangement == buttonSelection)
{
    // The A button is pressed and the B button is released (B is not pressed).
}

Bu formül, durumlarının herhangi bir düzenlemesindeki herhangi bir sayıda düğmeyi test etmek için uygulanabilir.

Pilin durumunu alma

IGameControllerBatteryInfo arabirimini uygulayan herhangi bir oyun denetleyicisi için, denetleyicideki pil hakkında bilgi sağlayan bir BatteryReport nesnesi almak için denetleyici örneğinde TryGetBatteryReport çağırabilirsiniz. Pilin şarj hızı (ChargeRateInMilliwatts), yeni bir pilin tahmini enerji kapasitesi (DesignCapacityInMilliwattHours) ve geçerli pilin tam olarak şarj edilmiş enerji kapasitesi (FullChargeCapacityInMilliwattHours) gibi özellikler elde edebilirsiniz.

Ayrıntılı pil raporlamayı destekleyen oyun denetleyicileri için, Pil bilgilerini almabölümünde açıklandığı gibi bu ve pil hakkında daha fazla bilgi edinebilirsiniz. Ancak çoğu oyun kumandası bu pil raporlama düzeyini desteklemez ve bunun yerine düşük maliyetli donanım kullanır. Bu denetleyiciler için aşağıdaki noktaları göz önünde bulundurmanız gerekir:

  • ChargeRateInMilliwatts ve DesignCapacityInMilliwattHours her zaman NULLolacaktır.

  • RemainingCapacityInMilliwattHours / FullChargeCapacityInMilliwattHourshesaplayarak pil yüzdesini alabilirsiniz. Bu özelliklerin değerlerini yoksayıp yalnızca hesaplanan yüzdeyle ilgilenmeniz gerekir.

  • Önceki madde işaretindeki yüzde her zaman aşağıdakilerden biri olacaktır.

    • 100% (Tam)
    • 70% (Orta)
    • 40% (Düşük)
    • 10% (Kritik)

Kodunuz kalan pil ömrü yüzdesine göre bazı eylemler (çizim kullanıcı arabirimi gibi) gerçekleştiriyorsa yukarıdaki değerlerle uyumlu olduğundan emin olun. Örneğin, oyun kumandasının pili az olduğunda oynatıcıyı uyarmak istiyorsanız, 10%ulaştığında bunu yapın.

Ayrıca bakınız