Menambahkan input dan interaktivitas ke sampel Marble Maze

Platform Windows Universal (UWP) berjalan di berbagai perangkat, seperti komputer desktop, laptop, dan tablet. Perangkat dapat memiliki sejumlah besar mekanisme input dan kontrol. Dokumen ini menjelaskan praktik utama yang perlu diingat saat Anda bekerja dengan perangkat input dan menunjukkan bagaimana Marble Maze menerapkan praktik ini.

Catatan

Kode sampel yang sesuai dengan dokumen ini ditemukan dalam sampel permainan DirectX Marble Maze.

  Berikut adalah beberapa poin penting yang dibahas dokumen ini ketika Anda bekerja dengan input dalam game Anda:

  • Jika memungkinkan, dukung beberapa perangkat input untuk memungkinkan game Anda mengakomodasi berbagai preferensi dan kemampuan yang lebih luas di antara pelanggan Anda. Meskipun pengontrol game dan penggunaan sensor bersifat opsional, kami sangat menyarankannya untuk meningkatkan pengalaman pemain. Kami merancang API pengontrol game dan sensor untuk membantu Anda mengintegrasikan perangkat input ini dengan lebih mudah.

  • Untuk menginisialisasi sentuhan, Anda harus mendaftar untuk peristiwa jendela seperti saat penunjuk diaktifkan, dirilis, dan dipindahkan. Untuk menginisialisasi akselerometer, buat objek Windows::D evices::Sensors::Accelerometer saat Anda menginisialisasi aplikasi. Pengontrol permainan tidak memerlukan inisialisasi.

  • Untuk game pemain tunggal, pertimbangkan apakah akan menggabungkan input dari semua kemungkinan pengontrol. Dengan cara ini, Anda tidak perlu melacak input apa yang berasal dari pengontrol mana. Atau, cukup lacak input hanya dari pengontrol yang terakhir ditambahkan, seperti yang kita lakukan dalam sampel ini.

  • Proses peristiwa Windows sebelum Anda memproses perangkat input.

  • Pengontrol game dan akselerometer mendukung polling. Artinya, Anda dapat melakukan polling data saat membutuhkannya. Untuk sentuhan, rekam peristiwa sentuh dalam struktur data yang tersedia untuk kode pemrosesan input Anda.

  • Pertimbangkan apakah akan menormalkan nilai input ke format umum. Melakukannya dapat menyederhanakan bagaimana input ditafsirkan oleh komponen lain dari permainan Anda, seperti simulasi fisika, dan dapat mempermudah penulisan game yang bekerja pada resolusi layar yang berbeda.

Perangkat input yang didukung oleh Marble Maze

Marble Maze mendukung pengontrol permainan, mouse, dan sentuhan untuk memilih item menu, dan pengontrol permainan, mouse, sentuhan, dan akselerometer untuk mengontrol permainan permainan. Marble Maze menggunakan Windows::Gaming::Input API untuk melakukan polling pengontrol untuk input. Sentuhan memungkinkan aplikasi melacak dan merespons input ujung jari. Akselerometer adalah sensor yang mengukur kekuatan yang diterapkan di sepanjang sumbu X, Y, dan Z. Dengan menggunakan Windows Runtime, Anda dapat melakukan polling status perangkat akselerometer saat ini, serta menerima peristiwa sentuhan melalui mekanisme penanganan peristiwa Windows Runtime.

Catatan

Dokumen ini menggunakan sentuhan untuk merujuk ke input dan penunjuk sentuhan dan mouse untuk merujuk ke perangkat apa pun yang menggunakan peristiwa pointer. Karena sentuhan dan mouse menggunakan peristiwa pointer standar, Anda dapat menggunakan salah satu perangkat untuk memilih item menu dan mengontrol permainan permainan.

 

Catatan

Manifes paket menetapkan Lanskap sebagai satu-satunya rotasi yang didukung untuk permainan untuk mencegah orientasi berubah ketika Anda memutar perangkat untuk menggulung marmer. Untuk melihat manifes paket, buka Package.appxmanifest di Penjelajah Solusi di Visual Studio.

 

Menginisialisasi perangkat input

Pengontrol permainan tidak memerlukan inisialisasi. Untuk menginisialisasi sentuhan, Anda harus mendaftar untuk peristiwa windowing seperti saat penunjuk diaktifkan (misalnya, pemutar menekan tombol mouse atau menyentuh layar), dilepaskan, dan dipindahkan. Untuk menginisialisasi akselerometer, Anda harus membuat objek Windows::D evices::Sensors::Accelerometer saat Menginisialisasi aplikasi.

Contoh berikut menunjukkan bagaimana metode App::SetWindow mendaftar untuk Windows::UI::Core::CoreWindow::P ointerPressed, Windows::UI::Core::CoreWindow::P ointerReleased, dan Windows::UI::Core::CoreWindow::P ointerMoved pointer events. Peristiwa ini didaftarkan selama inisialisasi aplikasi dan sebelum perulangan game.

Peristiwa ini ditangani dalam utas terpisah yang memanggil penanganan aktivitas.

Untuk informasi selengkapnya tentang bagaimana aplikasi diinisialisasi, lihat Struktur aplikasi Marble Maze.

window->PointerPressed += ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(
    this, 
    &App::OnPointerPressed);

window->PointerReleased += ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(
    this, 
    &App::OnPointerReleased);

window->PointerMoved += ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(
    this, 
    &App::OnPointerMoved);

Kelas MarbleMazeMain juga membuat objek std::map untuk mengadakan peristiwa sentuhan. Kunci untuk objek peta ini adalah nilai yang secara unik mengidentifikasi penunjuk input. Setiap kunci memetakan ke jarak antara setiap titik sentuh dan bagian tengah layar. Marble Maze nanti menggunakan nilai-nilai ini untuk menghitung jumlah di mana labirin miring.

typedef std::map<int, XMFLOAT2> TouchMap;
TouchMap        m_touches;

Kelas MarbleMazeMain juga menyimpan objek Accelerometer .

Windows::Devices::Sensors::Accelerometer^           m_accelerometer;

Objek Akselerometer diinisialisasi dalam konstruktor MarbleMazeMain , seperti yang ditunjukkan dalam contoh berikut. Metode Windows::D evices::Sensors::Accelerometer::GetDefault mengembalikan instans akselerometer default. Jika tidak ada akselerometer default, Accelerometer::GetDefault mengembalikan nullptr.

// Returns accelerometer ref if there is one; nullptr otherwise.
m_accelerometer = Windows::Devices::Sensors::Accelerometer::GetDefault();

Anda dapat menggunakan mouse, sentuhan, atau pengontrol permainan untuk menavigasi menu, sebagai berikut:

  • Gunakan pad arah untuk mengubah item menu aktif.
  • Gunakan sentuhan, tombol A, atau tombol Menu untuk memilih item menu atau menutup menu saat ini, seperti tabel skor tinggi.
  • Gunakan tombol Menu untuk menjeda atau melanjutkan permainan.
  • Klik item menu dengan mouse untuk memilih tindakan tersebut.

Melacak input pengontrol game

Untuk melacak gamepad yang saat ini terhubung ke perangkat, MarbleMazeMain mendefinisikan variabel anggota, m_myGamepads, yang merupakan koleksi objek Windows::Gaming::Input::Gamepad. Ini diinisialisasi dalam konstruktor seperti:

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

for (auto gamepad : Gamepad::Gamepads)
{
    m_myGamepads->Append(gamepad);
}

Selain itu, konstruktor MarbleMazeMain mendaftarkan peristiwa saat gamepad ditambahkan atau dihapus:

Gamepad::GamepadAdded += 
    ref new EventHandler<Gamepad^>([=](Platform::Object^, Gamepad^ args)
{
    m_myGamepads->Append(args);
    m_currentGamepadNeedsRefresh = true;
});

Gamepad::GamepadRemoved += 
    ref new EventHandler<Gamepad ^>([=](Platform::Object^, Gamepad^ args)
{
    unsigned int indexRemoved;

    if (m_myGamepads->IndexOf(args, &indexRemoved))
    {
        m_myGamepads->RemoveAt(indexRemoved);
        m_currentGamepadNeedsRefresh = true;
    }
});

Ketika gamepad ditambahkan, gamepad ditambahkan ke m_myGamepads; ketika gamepad dihapus, kami memeriksa apakah gamepad berada di m_myGamepads, dan jika ya, kami menghapusnya. Dalam kedua kasus, kita mengatur m_currentGamepadNeedsRefresh ke true, menunjukkan bahwa kita perlu menetapkan ulang m_gamepad.

Terakhir, kami menetapkan gamepad untuk m_gamepad dan mengatur m_currentGamepadNeedsRefresh ke false:

m_gamepad = GetLastGamepad();
m_currentGamepadNeedsRefresh = false;

Dalam metode Pembaruan, kita memeriksa apakah m_gamepad perlu ditetapkan kembali:

if (m_currentGamepadNeedsRefresh)
{
    auto mostRecentGamepad = GetLastGamepad();

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

    m_currentGamepadNeedsRefresh = false;
}

Jika m_gamepad perlu ditetapkan kembali, kami menetapkan gamepad yang terakhir ditambahkan, menggunakan GetLastGamepad, yang didefinisikan seperti itu:

Gamepad^ MarbleMaze::MarbleMazeMain::GetLastGamepad()
{
    Gamepad^ gamepad = nullptr;

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

    return gamepad;
}

Metode ini hanya mengembalikan gamepad terakhir dalam m_myGamepads.

Anda dapat menyambungkan hingga empat pengontrol permainan ke perangkat Windows 10. Untuk menghindari harus mencari tahu pengontrol mana yang aktif, kami hanya melacak gamepad yang terakhir ditambahkan. Jika game mendukung lebih dari satu pemain, anda harus melacak input untuk setiap pemain secara terpisah.

Metode MarbleMazeMain::Update melakukan polling pada gamepad untuk input:

if (m_gamepad != nullptr)
{
    m_oldReading = m_newReading;
    m_newReading = m_gamepad->GetCurrentReading();
}

Kami melacak pembacaan input yang kami dapatkan di bingkai terakhir dengan m_oldReading, dan pembacaan input terbaru dengan m_newReading, yang kami dapatkan dengan memanggil Gamepad::GetCurrentReading. Ini mengembalikan objek GamepadReading , yang berisi informasi tentang status gamepad saat ini.

Untuk memeriksa apakah tombol hanya ditekan atau dirilis, kami menentukan MarbleMazeMain::ButtonJustPressed dan MarbleMazeMain::ButtonJustReleased, yang membandingkan pembacaan tombol dari bingkai ini dan bingkai terakhir. Dengan cara ini, kita dapat melakukan tindakan hanya pada saat tombol awalnya ditekan atau dirilis, dan bukan saat ditahan:

bool MarbleMaze::MarbleMazeMain::ButtonJustPressed(GamepadButtons selection)
{
    bool newSelectionPressed = (selection == (m_newReading.Buttons & selection));
    bool oldSelectionPressed = (selection == (m_oldReading.Buttons & selection));
    return newSelectionPressed && !oldSelectionPressed;
}

bool MarbleMaze::MarbleMazeMain::ButtonJustReleased(GamepadButtons selection)
{
    bool newSelectionReleased = 
        (GamepadButtons::None == (m_newReading.Buttons & selection));

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

    return newSelectionReleased && !oldSelectionReleased;
}

Pembacaan GamepadButtons dibandingkan dengan menggunakan operasi bitwise—kami memeriksa apakah tombol ditekan menggunakan bitwise dan (&). Kami menentukan apakah tombol hanya ditekan atau dirilis dengan membandingkan pembacaan lama dan pembacaan baru.

Dengan menggunakan metode di atas, kami memeriksa apakah tombol tertentu telah ditekan dan melakukan tindakan yang sesuai yang harus terjadi. Misalnya, ketika tombol Menu (GamepadButtons::Menu) ditekan, status permainan berubah dari aktif menjadi dijeda atau dijeda menjadi aktif.

if (ButtonJustPressed(GamepadButtons::Menu) || m_pauseKeyPressed)
{
    m_pauseKeyPressed = false;

    if (m_gameState == GameState::InGameActive)
    {
        SetGameState(GameState::InGamePaused);
    }  
    else if (m_gameState == GameState::InGamePaused)
    {
        SetGameState(GameState::InGameActive);
    }
}

Kami juga memeriksa apakah pemain menekan tombol Tampilan, dalam hal ini kami memulai ulang permainan atau menghapus tabel skor tinggi:

if (ButtonJustPressed(GamepadButtons::View) || m_homeKeyPressed)
{
    m_homeKeyPressed = false;

    if (m_gameState == GameState::InGameActive ||
        m_gameState == GameState::InGamePaused ||
        m_gameState == GameState::PreGameCountdown)
    {
        SetGameState(GameState::MainMenu);
        m_inGameStopwatchTimer.SetVisible(false);
        m_preGameCountdownTimer.SetVisible(false);
    }
    else if (m_gameState == GameState::HighScoreDisplay)
    {
        m_highScoreTable.Reset();
    }
}

Jika menu utama aktif, item menu aktif berubah saat pad arah ditekan ke atas atau ke bawah. Jika pengguna memilih pilihan saat ini, elemen UI yang sesuai ditandai sebagai dipilih.

// Handle menu navigation.
bool chooseSelection = 
    (ButtonJustPressed(GamepadButtons::A) 
    || ButtonJustPressed(GamepadButtons::Menu));

bool moveUp = ButtonJustPressed(GamepadButtons::DPadUp);
bool moveDown = ButtonJustPressed(GamepadButtons::DPadDown);

switch (m_gameState)
{
case GameState::MainMenu:
    if (chooseSelection)
    {
        m_audio.PlaySoundEffect(MenuSelectedEvent);
        if (m_startGameButton.GetSelected())
        {
            m_startGameButton.SetPressed(true);
        }
        if (m_highScoreButton.GetSelected())
        {
            m_highScoreButton.SetPressed(true);
        }
    }
    if (moveUp || moveDown)
    {
        m_startGameButton.SetSelected(!m_startGameButton.GetSelected());
        m_highScoreButton.SetSelected(!m_startGameButton.GetSelected());
        m_audio.PlaySoundEffect(MenuChangeEvent);
    }
    break;

case GameState::HighScoreDisplay:
    if (chooseSelection || anyPoints)
    {
        SetGameState(GameState::MainMenu);
    }
    break;

case GameState::PostGameResults:
    if (chooseSelection || anyPoints)
    {
        SetGameState(GameState::HighScoreDisplay);
    }
    break;

case GameState::InGamePaused:
    if (m_pausedText.IsPressed())
    {
        m_pausedText.SetPressed(false);
        SetGameState(GameState::InGameActive);
    }
    break;
}

Sentuhan pelacakan dan input mouse

Untuk input sentuhan dan mouse, item menu dipilih saat pengguna menyentuh atau mengkliknya. Contoh berikut menunjukkan bagaimana metode MarbleMazeMain::Update memproses input pointer untuk memilih item menu. Variabel anggota m_pointQueue melacak lokasi tempat pengguna menyentuh atau mengklik layar. Cara Marble Maze mengumpulkan input pointer dijelaskan secara lebih rinci nanti dalam dokumen ini di bagian Memproses input pointer.

// Check whether the user chose a button from the UI. 
bool anyPoints = !m_pointQueue.empty();
while (!m_pointQueue.empty())
{
    UserInterface::GetInstance().HitTest(m_pointQueue.front());
    m_pointQueue.pop();
}

Metode UserInterface::HitTest menentukan apakah titik yang disediakan terletak di batas elemen UI apa pun. Elemen UI apa pun yang lulus pengujian ini ditandai sebagai disentuh. Metode ini menggunakan fungsi pembantu PointInRect untuk menentukan apakah titik yang disediakan terletak di batas setiap elemen UI.

void UserInterface::HitTest(D2D1_POINT_2F point)
{
    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        if (!(*iter)->IsVisible())
            continue;

        TextButton* textButton = dynamic_cast<TextButton*>(*iter);
        if (textButton != nullptr)
        {
            D2D1_RECT_F bounds = (*iter)->GetBounds();
            textButton->SetPressed(PointInRect(point, bounds));
        }
    }
}

Memperbarui status permainan

Setelah metode MarbleMazeMain::Update memproses pengontrol dan input sentuhan, metode ini memperbarui status permainan jika ada tombol yang ditekan.

// Update the game state if the user chose a menu option. 
if (m_startGameButton.IsPressed())
{
    SetGameState(GameState::PreGameCountdown);
    m_startGameButton.SetPressed(false);
}
if (m_highScoreButton.IsPressed())
{
    SetGameState(GameState::HighScoreDisplay);
    m_highScoreButton.SetPressed(false);
}

Mengontrol permainan

Perulangan game dan metode MarbleMazeMain::Update bekerja sama untuk memperbarui status objek game. Jika gim menerima input dari beberapa perangkat, Anda dapat mengakumulasi input dari semua perangkat ke dalam satu set variabel sehingga Anda dapat menulis kode yang lebih mudah dipertahankan. Metode MarbleMazeMain::Update mendefinisikan satu set variabel yang mengakumulasi pergerakan dari semua perangkat.

float combinedTiltX = 0.0f;
float combinedTiltY = 0.0f;

Mekanisme input dapat bervariasi dari satu perangkat input ke perangkat input lainnya. Misalnya, input pointer ditangani dengan menggunakan model penanganan peristiwa Windows Runtime. Sebaliknya, Anda melakukan polling untuk data input dari pengontrol game saat Anda membutuhkannya. Kami menyarankan agar Anda selalu mengikuti mekanisme input yang ditentukan untuk perangkat tertentu. Bagian ini menjelaskan bagaimana Marble Maze membaca input dari setiap perangkat, cara memperbarui nilai input gabungan, dan bagaimana ia menggunakan nilai input gabungan untuk memperbarui status permainan.

Memproses input penunjuk

Saat Anda bekerja dengan input pointer, panggil metode Windows::UI::Core::CoreDispatcher::P rocessEvents untuk memproses peristiwa jendela. Panggil metode ini dalam perulangan game Anda sebelum Anda memperbarui atau merender adegan. Marble Maze memanggil ini dalam metode App::Run :

while (!m_windowClosed)
{
    if (m_windowVisible)
    {
        CoreWindow::GetForCurrentThread()->
            Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

        m_main->Update();

        if (m_main->Render())
        {
            m_deviceResources->Present();
        }
    }
    else
    {
        CoreWindow::GetForCurrentThread()->
            Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
    }
}

Jika jendela terlihat, kami melewati CoreProcessEventsOption::P rocessAllIfPresent ke ProcessEvents untuk memproses semua peristiwa yang diantrekan dan segera kembali; jika tidak, kami melewati CoreProcessEventsOption::P rocessOneAndAllPending untuk memproses semua peristiwa yang diantrekan dan menunggu peristiwa baru berikutnya. Setelah peristiwa diproses, Marble Maze merender dan menyajikan bingkai berikutnya.

Windows Runtime memanggil handler terdaftar untuk setiap kejadian yang terjadi. Metode App::SetWindow mendaftar untuk peristiwa dan meneruskan informasi penunjuk ke kelas MarbleMazeMain .

void App::OnPointerPressed(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->AddTouch(args->CurrentPoint->PointerId, args->CurrentPoint->Position);
}

void App::OnPointerReleased(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->RemoveTouch(args->CurrentPoint->PointerId);
}

void App::OnPointerMoved(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->UpdateTouch(args->CurrentPoint->PointerId, args->CurrentPoint->Position);
}

Kelas MarbleMazeMain bereaksi terhadap peristiwa penunjuk dengan memperbarui objek peta yang menyimpan peristiwa sentuh. Metode MarbleMazeMain::AddTouch dipanggil ketika pointer pertama kali ditekan, misalnya, ketika pengguna awalnya menyentuh layar pada perangkat yang mendukung sentuhan. Metode MarbleMazeMain::UpdateTouch dipanggil ketika posisi pointer bergerak. Metode MarbleMazeMain::RemoveTouch dipanggil saat pointer dirilis, misalnya, ketika pengguna berhenti menyentuh layar.

void MarbleMazeMain::AddTouch(int id, Windows::Foundation::Point point)
{
    m_touches[id] = PointToTouch(point, m_deviceResources->GetLogicalSize());

    m_pointQueue.push(D2D1::Point2F(point.X, point.Y));
}

void MarbleMazeMain::UpdateTouch(int id, Windows::Foundation::Point point)
{
    if (m_touches.find(id) != m_touches.end())
        m_touches[id] = PointToTouch(point, m_deviceResources->GetLogicalSize());
}

void MarbleMazeMain::RemoveTouch(int id)
{
    m_touches.erase(id);
}

Fungsi PointToTouch menerjemahkan posisi penunjuk saat ini sehingga asal berada di tengah layar, lalu menskalakan koordinat sehingga berkisar antara -1.0 dan +1.0. Ini membuatnya lebih mudah untuk menghitung kempis labirin dengan cara yang konsisten di berbagai metode input.

inline XMFLOAT2 PointToTouch(Windows::Foundation::Point point, Windows::Foundation::Size bounds)
{
    float touchRadius = min(bounds.Width, bounds.Height);
    float dx = (point.X - (bounds.Width / 2.0f)) / touchRadius;
    float dy = ((bounds.Height / 2.0f) - point.Y) / touchRadius;

    return XMFLOAT2(dx, dy);
}

Metode MarbleMazeMain::Update memperbarui nilai input gabungan dengan menaikkan faktor miring dengan nilai penskalaan konstan. Nilai penskalakan ini ditentukan dengan bereksperimen dengan beberapa nilai yang berbeda.

// Account for touch input.
for (TouchMap::const_iterator iter = m_touches.cbegin(); 
    iter != m_touches.cend(); 
    ++iter)
{
    combinedTiltX += iter->second.x * m_touchScaleFactor;
    combinedTiltY += iter->second.y * m_touchScaleFactor;
}

Memproses input akselerometer

Untuk memproses input akselerometer, metode MarbleMazeMain::Update memanggil metode Windows::D evices::Sensors::Accelerometer::GetCurrentReading . Metode ini mengembalikan objek Windows::D evices::Sensors::AccelerometerReading , yang mewakili pembacaan akselerometer. Properti Windows::D evices::Sensors::AccelerometerReading::AccelerationX and Windows::D evices::Sensors::AccelerometerReading::AccelerationY memegang akselerasi g-force di sepanjang sumbu X dan Y.

Contoh berikut menunjukkan bagaimana metode MarbleMazeMain::Update melakukan polling akselerometer dan memperbarui nilai input gabungan. Saat Anda memiringkan perangkat, gravitasi menyebabkan marmer bergerak lebih cepat.

// Account for sensors.
if (m_accelerometer != nullptr)
{
    Windows::Devices::Sensors::AccelerometerReading^ reading =
        m_accelerometer->GetCurrentReading();

    if (reading != nullptr)
    {
        combinedTiltX += 
            static_cast<float>(reading->AccelerationX) * m_accelerometerScaleFactor;

        combinedTiltY += 
            static_cast<float>(reading->AccelerationY) * m_accelerometerScaleFactor;
    }
}

Karena Anda tidak dapat yakin bahwa akselerometer ada di komputer pengguna, selalu pastikan bahwa Anda memiliki objek Akselerometer yang valid sebelum Anda melakukan polling akselerometer.

Memproses input pengontrol game

Dalam metode MarbleMazeMain::Update, kami menggunakan m_newReading untuk memproses input dari tongkat analog kiri:

float leftStickX = static_cast<float>(m_newReading.LeftThumbstickX);
float leftStickY = static_cast<float>(m_newReading.LeftThumbstickY);

auto oppositeSquared = leftStickY * leftStickY;
auto adjacentSquared = leftStickX * leftStickX;

if ((oppositeSquared + adjacentSquared) > m_deadzoneSquared)
{
    combinedTiltX += leftStickX * m_controllerScaleFactor;
    combinedTiltY += leftStickY * m_controllerScaleFactor;
}

Kami memeriksa apakah input dari tongkat analog kiri berada di luar zona mati, dan jika ya, kami menambahkannya ke combinedTiltX dan combinedTiltY (dikalikan dengan faktor skala) untuk memiringkan tahap.

Penting

Ketika Anda bekerja dengan pengontrol permainan, selalu perhitungkan zona mati. Zona mati mengacu pada varians di antara gamepad dalam sensitivitas mereka terhadap gerakan awal. Di beberapa pengontrol, gerakan kecil dapat menghasilkan tidak ada pembacaan, tetapi di yang lain dapat menghasilkan pembacaan yang terukur. Untuk memperhitungkan hal ini dalam game Anda, buat zona non-gerakan untuk gerakan thumbstick awal. Untuk informasi selengkapnya tentang zona mati, lihat Membaca thumbstick.

 

Menerapkan input ke status permainan

Perangkat melaporkan nilai input dengan cara yang berbeda. Misalnya, input pointer mungkin berada dalam koordinat layar, dan input pengontrol mungkin dalam format yang sama sekali berbeda. Salah satu tantangan dengan menggabungkan input dari beberapa perangkat ke dalam satu set nilai input adalah normalisasi, atau mengonversi nilai ke format umum. Marble Maze menormalkan nilai dengan menskalakannya ke rentang [-1.0, 1.0]. Fungsi PointToTouch , yang sebelumnya dijelaskan di bagian ini, mengonversi koordinat layar ke nilai yang dinormalisasi yang berkisar sekitar antara -1.0 dan +1.0.

Tip

Bahkan jika aplikasi Anda menggunakan satu metode input, kami sarankan Anda selalu menormalkan nilai input. Melakukannya dapat menyederhanakan bagaimana input ditafsirkan oleh komponen lain dari permainan Anda, seperti simulasi fisika, dan membuatnya lebih mudah untuk menulis game yang bekerja pada resolusi layar yang berbeda.

 

Setelah input proses metode MarbleMazeMain::Update, metode ini membuat vektor yang mewakili efek kempis labirin pada marmer. Contoh berikut menunjukkan bagaimana Marble Maze menggunakan fungsi XMVector3Normalize untuk membuat vektor gravitasi yang dinormalisasi. Variabel maxTilt membatasi jumlah di mana labirin memiringkan dan mencegah labirin memiringkan di sisinya.

const float maxTilt = 1.0f / 8.0f;

XMVECTOR gravity = XMVectorSet(
    combinedTiltX * maxTilt, 
    combinedTiltY * maxTilt, 
    1.0f, 
    0.0f);

gravity = XMVector3Normalize(gravity);

Untuk menyelesaikan pembaruan objek adegan, Marble Maze meneruskan vektor gravitasi yang diperbarui ke simulasi fisika, memperbarui simulasi fisika untuk waktu yang telah berlalu sejak bingkai sebelumnya, dan memperbarui posisi dan orientasi marmer. Jika marmer telah jatuh melalui labirin, metode MarbleMazeMain::Update menempatkan marmer kembali di titik pemeriksaan terakhir bahwa marmer menyentuh dan mengatur ulang status simulasi fisika.

XMFLOAT3A g;
XMStoreFloat3(&g, gravity);
m_physics.SetGravity(g);

if (m_gameState == GameState::InGameActive)
{
    // Only update physics when gameplay is active.
    m_physics.UpdatePhysicsSimulation(static_cast<float>(m_timer.GetElapsedSeconds()));

    // ...Code omitted for simplicity...

}

// ...Code omitted for simplicity...

// Check whether the marble fell off of the maze. 
const float fadeOutDepth = 0.0f;
const float resetDepth = 80.0f;
if (marblePosition.z >= fadeOutDepth)
{
    m_targetLightStrength = 0.0f;
}
if (marblePosition.z >= resetDepth)
{
    // Reset marble.
    memcpy(&marblePosition, &m_checkpoints[m_currentCheckpoint], sizeof(XMFLOAT3));
    oldMarblePosition = marblePosition;
    m_physics.SetPosition((const XMFLOAT3&)marblePosition);
    m_physics.SetVelocity(XMFLOAT3(0, 0, 0));
    m_lightStrength = 0.0f;
    m_targetLightStrength = 1.0f;

    m_resetCamera = true;
    m_resetMarbleRotation = true;
    m_audio.PlaySoundEffect(FallingEvent);
}

Bagian ini tidak menjelaskan cara kerja simulasi fisika. Untuk detail tentang itu, lihat Physics.h dan Physics.cpp di sumber Labirin Marmer.

Langkah berikutnya

Baca Menambahkan audio ke sampel Marble Maze untuk informasi tentang beberapa praktik utama yang perlu diingat saat Anda bekerja dengan audio. Dokumen ini membahas bagaimana Marble Maze menggunakan Microsoft Media Foundation dan XAudio2 untuk memuat, mencampur, dan memutar sumber daya audio.