Menambahkan kontrol

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.

[ Diperbarui untuk aplikasi UWP di Windows 10. Untuk artikel Windows 8.x, lihat arsip ]

Gim Platform Windows Universal (UWP) yang baik mendukung berbagai antarmuka. Pemain potensial mungkin memiliki Windows 10 di tablet tanpa tombol fisik, PC dengan pengontrol game terpasang, atau rig game desktop terbaru dengan mouse berkinerja tinggi dan keyboard game. Dalam permainan kami kontrol diimplementasikan di kelas MoveLookController. Kelas ini menggabungkan ketiga jenis input (mouse dan keyboard, sentuhan, dan gamepad) ke dalam satu pengontrol. Hasil akhirnya adalah penembak orang pertama yang menggunakan kontrol tampilan bergerak standar genre yang berfungsi dengan beberapa perangkat.

Catatan

Untuk informasi selengkapnya tentang kontrol, lihat Kontrol tampilan pemindahan untuk game dan Kontrol sentuh untuk game.

Tujuan

Pada titik ini kami memiliki permainan yang merender, tetapi kami tidak dapat memindahkan pemain kami di sekitar atau menembak target. Kami akan melihat bagaimana game kami mengimplementasikan kontrol tampilan pemindahan penembak orang pertama untuk jenis input berikut dalam game UWP DirectX kami.

  • Mouse dan keyboard
  • Sentuh
  • Gamepad

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.

Perilaku kontrol umum

Kontrol sentuh dan kontrol mouse/keyboard memiliki implementasi inti yang sangat mirip. Dalam aplikasi UWP, pointer hanyalah titik di layar. Anda dapat menggerakkannya dengan menggeser mouse atau menggeser jari di layar sentuh. Akibatnya, Anda dapat mendaftar untuk satu set peristiwa, dan tidak khawatir tentang apakah pemutar menggunakan mouse atau layar sentuh untuk memindahkan dan menekan penunjuk.

Ketika kelas MoveLookController dalam permainan sampel diinisialisasi, kelas ini mendaftar untuk empat peristiwa khusus pointer dan satu peristiwa khusus mouse:

Aktivitas Deskripsi
CoreWindow::P ointerPressed Tombol mouse kiri atau kanan ditekan (dan ditahan), atau permukaan sentuhan disentuh.
CoreWindow::P ointerMoved Mouse dipindahkan, atau tindakan seret dibuat pada permukaan sentuh.
CoreWindow::P ointerReleased Tombol mouse kiri dilepaskan, atau objek yang menghubungi permukaan sentuhan diangkat.
CoreWindow::P ointerExited Penunjuk bergerak keluar dari jendela utama.
Windows::D evices::Input::MouseMoved Mouse memindahkan jarak tertentu. Ketahuilah bahwa kita hanya tertarik pada nilai delta gerakan mouse, dan bukan posisi X-Y saat ini.

Penanganan aktivitas ini diatur untuk mulai mendengarkan input pengguna segera setelah MoveLookController diinisialisasi di jendela aplikasi.

void MoveLookController::InitWindow(_In_ CoreWindow const& window)
{
    ResetState();

    window.PointerPressed({ this, &MoveLookController::OnPointerPressed });

    window.PointerMoved({ this, &MoveLookController::OnPointerMoved });

    window.PointerReleased({ this, &MoveLookController::OnPointerReleased });

    window.PointerExited({ this, &MoveLookController::OnPointerExited });

    ...

    // There is a separate handler for mouse-only relative mouse movement events.
    MouseDevice::GetForCurrentView().MouseMoved({ this, &MoveLookController::OnMouseMoved });

    ...
}

Kode lengkap untuk InitWindow dapat dilihat di GitHub.

Untuk menentukan kapan game harus mendengarkan input tertentu, kelas MoveLookController memiliki tiga status khusus pengontrol, terlepas dari jenis pengontrol:

Provinsi Deskripsi
Tidak ada Ini adalah status yang diinisialisasi untuk pengontrol. Semua input diabaikan karena permainan tidak mengantisipasi input pengontrol apa pun.
WaitForInput Pengontrol sedang menunggu pemutar untuk mengakui pesan dari permainan dengan menggunakan klik mouse kiri, acara sentuh, ot tombol menu pada gamepad.
Aktif Pengontrol dalam mode bermain game aktif.

Status WaitForInput dan menjeda permainan

Permainan memasuki status WaitForInput ketika permainan telah dijeda. Ini terjadi ketika pemain memindahkan pointer di luar jendela utama permainan, atau menekan tombol jeda (tombol P atau tombol Mulai gamepad). MoveLookController mendaftarkan pers, dan menginformasikan perulangan permainan ketika memanggil metode IsPauseRequested. Pada titik itu jika IsPauseRequested mengembalikan true, perulangan game kemudian memanggil WaitForPress pada MoveLookController untuk memindahkan pengontrol ke status WaitForInput .

Setelah dalam status WaitForInput , game berhenti memproses hampir semua peristiwa input gameplay sampai kembali ke status Aktif . Pengecualian adalah tombol jeda, dengan menekan ini menyebabkan permainan kembali ke status aktif. Selain tombol jeda, agar permainan kembali ke status Aktif , pemutar perlu memilih item menu.

Status Aktif

Selama status Aktif , instans MoveLookController memproses peristiwa dari semua perangkat input yang diaktifkan dan menafsirkan niat pemutar. Akibatnya, ini memperbarui kecepatan dan melihat arah tampilan pemain dan berbagi data yang diperbarui dengan game setelah Pembaruan dipanggil dari perulangan game.

Semua input pointer dilacak dalam status Aktif , dengan ID pointer yang berbeda yang sesuai dengan tindakan pointer yang berbeda. Ketika peristiwa PointerPressed diterima, MoveLookController mendapatkan nilai ID pointer yang dibuat oleh jendela. ID penunjuk mewakili jenis input tertentu. Misalnya, pada perangkat multi-sentuh, mungkin ada beberapa input aktif yang berbeda secara bersamaan. ID digunakan untuk melacak input mana yang digunakan pemutar. Jika satu peristiwa berada di persegi panjang pemindahan layar sentuh, ID penunjuk ditetapkan untuk melacak peristiwa penunjuk apa pun di persegi panjang pemindahan. Peristiwa penunjuk lain di persegi panjang api dilacak secara terpisah, dengan ID penunjuk terpisah.

Catatan

Input dari mouse dan thumbstick kanan gamepad juga memiliki ID yang ditangani secara terpisah.

Setelah peristiwa pointer dipetakan ke tindakan permainan tertentu, saatnya untuk memperbarui data berbagi objek MoveLookController dengan perulangan game utama.

Ketika dipanggil, metode Pembaruan dalam permainan sampel memproses input dan memperbarui kecepatan dan melihat variabel arah (m_velocity dan m_lookdirection), yang kemudian diambil perulangan game dengan memanggil metode Velocity publik dan LookDirection.

Catatan

Detail selengkapnya tentang metode Pembaruan dapat dilihat nanti di halaman ini.

Perulangan permainan dapat menguji untuk melihat apakah pemain menembak dengan memanggil metode IsFiring pada instans MoveLookController . MoveLookController memeriksa untuk melihat apakah pemutar telah menekan tombol aktifkan pada salah satu dari tiga jenis input.

bool MoveLookController::IsFiring()
{
    if (m_state == MoveLookControllerState::Active)
    {
        if (m_autoFire)
        {
            return (m_fireInUse || (m_mouseInUse && m_mouseLeftInUse) || PollingFireInUse());
        }
        else
        {
            if (m_firePressed)
            {
                m_firePressed = false;
                return true;
            }
        }
    }
    return false;
}

Sekarang mari kita lihat implementasi masing-masing dari tiga jenis kontrol secara lebih rinci.

Menambahkan kontrol mouse relatif

Jika gerakan mouse terdeteksi, kita ingin menggunakan gerakan itu untuk menentukan nada baru dan yaw kamera. Kami melakukannya dengan menerapkan kontrol mouse relatif, di mana kami menangani jarak relatif yang telah dipindahkan mouse—delta antara awal gerakan dan pemberhentian—dibandingkan dengan merekam koordinat piksel x-y absolut gerakan.

Untuk melakukannya, kita mendapatkan perubahan dalam bidang X (gerakan horizontal) dan Y (gerakan vertikal) dengan memeriksa bidang MouseDelta::X dan MouseDelta::Y pada bidang Windows::D evice::Input::MouseEventArgs::MouseDelta argumen yang dikembalikan oleh peristiwa MouseMoved.

void MoveLookController::OnMouseMoved(
    _In_ MouseDevice const& /* mouseDevice */,
    _In_ MouseEventArgs const& args
    )
{
    // Handle Mouse Input via dedicated relative movement handler.

    switch (m_state)
    {
    case MoveLookControllerState::Active:
        XMFLOAT2 mouseDelta;
        mouseDelta.x = static_cast<float>(args.MouseDelta().X);
        mouseDelta.y = static_cast<float>(args.MouseDelta().Y);

        XMFLOAT2 rotationDelta;
        // Scale for control sensitivity.
        rotationDelta.x = mouseDelta.x * MoveLookConstants::RotationGain;
        rotationDelta.y = mouseDelta.y * MoveLookConstants::RotationGain;

        // Update our orientation based on the command.
        m_pitch -= rotationDelta.y;
        m_yaw += rotationDelta.x;

        // Limit pitch to straight up or straight down.
        float limit = XM_PI / 2.0f - 0.01f;
        m_pitch = __max(-limit, m_pitch);
        m_pitch = __min(+limit, m_pitch);

        // Keep longitude in sane range by wrapping.
        if (m_yaw > XM_PI)
        {
            m_yaw -= XM_PI * 2.0f;
        }
        else if (m_yaw < -XM_PI)
        {
            m_yaw += XM_PI * 2.0f;
        }
        break;
    }
}

Menambahkan dukungan sentuh

Kontrol sentuh sangat bagus untuk mendukung pengguna dengan tablet. Game ini mengumpulkan input sentuhan dengan zonasi area layar tertentu dengan masing-masing selaras dengan tindakan dalam game tertentu. Input sentuhan game ini menggunakan tiga zona.

move look touch layout

Perintah berikut meringkas perilaku kontrol sentuh kami. Input pengguna | Tindakan :------- | :-------- Pindahkan persegi panjang | Input sentuh dikonversi menjadi joystick virtual di mana gerakan vertikal akan diterjemahkan ke dalam gerakan posisi maju/mundur dan gerakan horizontal akan diterjemahkan ke dalam gerakan posisi kiri/kanan. Persegi panjang api | Tembakkan bola. Sentuh di luar bergerak dan menembakkan persegi panjang | Ubah rotasi (nada dan yaw) tampilan kamera.

MoveLookController memeriksa ID penunjuk untuk menentukan di mana peristiwa terjadi, dan mengambil salah satu tindakan berikut:

  • Jika peristiwa PointerMoved terjadi di persegi panjang pemindahan atau kebakaran, perbarui posisi penunjuk untuk pengontrol.
  • Jika peristiwa PointerMoved terjadi di suatu tempat di sisa layar (didefinisikan sebagai kontrol tampilan), hitung perubahan dalam nada dan yaw dari vektor arah tampilan.

Setelah kami menerapkan kontrol sentuh kami, persegi panjang yang kami gambar sebelumnya menggunakan Direct2D akan menunjukkan kepada pemain tempat zona bergerak, menembak, dan melihat.

touch controls

Sekarang mari kita lihat bagaimana kita menerapkan setiap kontrol.

Memindahkan dan menembakkan pengontrol

Persegi panjang pengontrol pemindahan di kuadrian kiri bawah layar digunakan sebagai pad arah. Menggeser jempol ke kiri dan kanan dalam ruang ini akan menggerakkan pemutar ke kiri dan kanan, sementara ke atas dan bawah menggerakkan kamera maju dan mundur. Setelah menyiapkannya, mengetuk pengontrol api di kuadran kanan bawah layar akan menembakkan bola.

Metode SetMoveRect dan SetFireRect membuat persegi panjang input kami, mengambil dua vektor 2D untuk menentukan posisi sudut kiri atas dan kanan bawah setiap persegi panjang di layar.

Parameter kemudian ditetapkan ke m_fireUpperLeft dan m_fireLowerRight yang akan membantu kami menentukan apakah pengguna menyentuh di dalam persegi panjang.

m_fireUpperLeft = upperLeft;
m_fireLowerRight = lowerRight;

Jika layar diubah ukurannya, persegi panjang ini digambar ulang ke ukuran yang disetujui.

Sekarang setelah kami melakukan zona kontrol, saatnya untuk menentukan kapan pengguna benar-benar menggunakannya. Untuk melakukan ini, kami menyiapkan beberapa penanganan aktivitas di metode MoveLookController::InitWindow saat pengguna menekan, memindahkan, atau merilis pointer mereka.

window.PointerPressed({ this, &MoveLookController::OnPointerPressed });

window.PointerMoved({ this, &MoveLookController::OnPointerMoved });

window.PointerReleased({ this, &MoveLookController::OnPointerReleased });

Pertama-tama kita akan menentukan apa yang terjadi ketika pengguna pertama kali menekan dalam pemindahan atau menembakkan persegi panjang menggunakan metode OnPointerPressed. Di sini kita memeriksa di mana mereka menyentuh kontrol dan apakah pointer sudah ada di pengontrol itu. Jika ini adalah jari pertama yang menyentuh kontrol tertentu, kami melakukan hal berikut.

  • Simpan lokasi touchdown di m_moveFirstDown atau m_fireFirstDown sebagai vektor 2D.
  • Tetapkan ID penunjuk ke m_movePointerID atau m_firePointerID.
  • Atur bendera InUse yang tepat (m_moveInUse atau m_fireInUse) karena true kita sekarang memiliki penunjuk aktif untuk kontrol tersebut.
PointerPoint point = args.CurrentPoint();
uint32_t pointerID = point.PointerId();
Point pointerPosition = point.Position();
PointerPointProperties pointProperties = point.Properties();
auto pointerDevice = point.PointerDevice();
auto pointerDeviceType = pointerDevice.PointerDeviceType();

XMFLOAT2 position = XMFLOAT2(pointerPosition.X, pointerPosition.Y);

...
case MoveLookControllerState::Active:
    switch (pointerDeviceType)
    {
    case winrt::Windows::Devices::Input::PointerDeviceType::Touch:
        // Check to see if this pointer is in the move control.
        if (position.x > m_moveUpperLeft.x &&
            position.x < m_moveLowerRight.x &&
            position.y > m_moveUpperLeft.y &&
            position.y < m_moveLowerRight.y)
        {
            // If no pointer is in this control yet.
            if (!m_moveInUse)
            {
                // Process a DPad touch down event.
                // Save the location of the initial contact
                m_moveFirstDown = position;
                // Store the pointer using this control
                m_movePointerID = pointerID;
                // Set InUse flag to signal there is an active move pointer
                m_moveInUse = true;
            }
        }
        // Check to see if this pointer is in the fire control.
        else if (position.x > m_fireUpperLeft.x &&
            position.x < m_fireLowerRight.x &&
            position.y > m_fireUpperLeft.y &&
            position.y < m_fireLowerRight.y)
        {
            if (!m_fireInUse)
            {
                // Save the location of the initial contact
                m_fireLastPoint = position;
                // Store the pointer using this control
                m_firePointerID = pointerID;
                // Set InUse flag to signal there is an active fire pointer
                m_fireInUse = true;
                ...
            }
        }
        ...

Sekarang setelah kita menentukan apakah pengguna menyentuh gerakan atau kontrol tembakan, kita melihat apakah pemain melakukan gerakan dengan jari yang ditekan. Dengan menggunakan metode MoveLookController::OnPointerMoved, kami memeriksa pointer apa yang telah dipindahkan dan kemudian menyimpan posisi barunya sebagai vektor 2D.

PointerPoint point = args.CurrentPoint();
uint32_t pointerID = point.PointerId();
Point pointerPosition = point.Position();
PointerPointProperties pointProperties = point.Properties();
auto pointerDevice = point.PointerDevice();

// convert to allow math
XMFLOAT2 position = XMFLOAT2(pointerPosition.X, pointerPosition.Y);

switch (m_state)
{
case MoveLookControllerState::Active:
    // Decide which control this pointer is operating.

    // Move control
    if (pointerID == m_movePointerID)
    {
        // Save the current position.
        m_movePointerPosition = position;
    }
    // Look control
    else if (pointerID == m_lookPointerID)
    {
        ...
    }
    // Fire control
    else if (pointerID == m_firePointerID)
    {
        m_fireLastPoint = position;
    }
    ...

Setelah pengguna membuat gerakan mereka dalam kontrol, mereka akan merilis pointer. Menggunakan metode MoveLookController::OnPointerReleased, kami menentukan pointer mana yang telah dirilis dan melakukan serangkaian reset.

Jika kontrol pemindahan telah dirilis, kami melakukan hal berikut.

  • Atur kecepatan pemain ke 0 segala arah untuk mencegah mereka bergerak dalam permainan.
  • Beralih m_moveInUse ke false karena pengguna tidak lagi menyentuh pengontrol pemindahan.
  • Atur ID penunjuk pemindahan ke 0 karena tidak ada lagi penunjuk di pengontrol pemindahan.
if (pointerID == m_movePointerID)
{
    // Stop on release.
    m_velocity = XMFLOAT3(0, 0, 0);
    m_moveInUse = false;
    m_movePointerID = 0;
}

Untuk kontrol kebakaran, jika telah dirilis semua yang kita lakukan adalah mengalihkan bendera m_fireInUse ke false dan ID penunjuk api ke 0 karena tidak ada lagi penunjuk di kontrol kebakaran.

else if (pointerID == m_firePointerID)
{
    m_fireInUse = false;
    m_firePointerID = 0;
}

Pengontrol tampilan

Kami memperlakukan peristiwa penunjuk perangkat sentuh untuk wilayah layar yang tidak digunakan sebagai pengontrol tampilan. Menggeser jari Anda di sekitar zona ini mengubah pitch dan yaw (rotasi) kamera pemutar.

Jika peristiwa MoveLookController::OnPointerPressed dinaikkan pada perangkat sentuh di wilayah ini dan status permainan diatur ke Aktif, itu diberi ID pointer.

// If no pointer is in this control yet.
if (!m_lookInUse)
{
    // Save point for later move.
    m_lookLastPoint = position;
    // Store the pointer using this control.
    m_lookPointerID = pointerID;
    // These are for smoothing.
    m_lookLastDelta.x = m_lookLastDelta.y = 0;
    m_lookInUse = true;
}

Di sini MoveLookController menetapkan ID penunjuk untuk penunjuk yang menembakkan peristiwa ke variabel tertentu yang sesuai dengan wilayah tampilan. Dalam kasus sentuhan yang terjadi di wilayah tampilan, variabel m_lookPointerID diatur ke ID penunjuk yang menembakkan peristiwa. Variabel boolean, m_lookInUse, juga diatur untuk menunjukkan bahwa kontrol belum dirilis.

Sekarang, mari kita lihat bagaimana permainan sampel menangani peristiwa layar sentuh PointerMoved.

Dalam metode MoveLookController::OnPointerMoved, kami memeriksa untuk melihat jenis ID pointer apa yang telah ditetapkan ke peristiwa. Jika m_lookPointerID, kami menghitung perubahan posisi pointer. Kami kemudian menggunakan delta ini untuk menghitung berapa banyak rotasi yang harus berubah. Akhirnya kita berada pada titik di mana kita dapat memperbarui m_pitch dan m_yaw yang akan digunakan dalam permainan untuk mengubah rotasi pemain.

// This is the look pointer.
else if (pointerID == m_lookPointerID)
{
    // Look control.
    XMFLOAT2 pointerDelta;
    // How far did the pointer move?
    pointerDelta.x = position.x - m_lookLastPoint.x;
    pointerDelta.y = position.y - m_lookLastPoint.y;

    XMFLOAT2 rotationDelta;
    // Scale for control sensitivity.
    rotationDelta.x = pointerDelta.x * MoveLookConstants::RotationGain;
    rotationDelta.y = pointerDelta.y * MoveLookConstants::RotationGain;
    // Save for next time through.
    m_lookLastPoint = position;

    // Update our orientation based on the command.
    m_pitch -= rotationDelta.y;
    m_yaw += rotationDelta.x;

    // Limit pitch to straight up or straight down.
    float limit = XM_PI / 2.0f - 0.01f;
    m_pitch = __max(-limit, m_pitch);
    m_pitch = __min(+limit, m_pitch);
    ...
}

Bagian terakhir yang akan kita lihat adalah bagaimana permainan sampel menangani peristiwa layar sentuh PointerReleased. Setelah pengguna menyelesaikan gerakan sentuh dan menghapus jari mereka dari layar, MoveLookController::OnPointerReleased dimulai. Jika ID pointer yang mengaktifkan peristiwa PointerReleased adalah ID dari pointer pemindahan yang direkam sebelumnya, MoveLookController mengatur kecepatan ke 0 karena pemutar telah berhenti menyentuh area tampilan.

else if (pointerID == m_lookPointerID)
{
    m_lookInUse = false;
    m_lookPointerID = 0;
}

Menambahkan dukungan mouse dan keyboard

Permainan ini memiliki tata letak kontrol berikut untuk keyboard dan tetikus.

Masukan pengguna Tindakan
W Pindahkan pemutar ke depan
A Pindahkan pemutar ke kiri
S Pindahkan pemutar ke belakang
D Pindahkan pemutar ke kanan
X Pindahkan tampilan ke atas
Bilah spasi Pindahkan tampilan ke bawah
P Jeda permainan
Gerakan mouse Mengubah rotasi (nada dan yaw) tampilan kamera
Tombol mouse kiri Menembakkan bola

Untuk menggunakan keyboard, contoh game mendaftarkan dua peristiwa baru, CoreWindow::KeyUp dan CoreWindow::KeyDown, dalam metode MoveLookController::InitWindow. Peristiwa ini menangani pers dan rilis kunci.

window.KeyDown({ this, &MoveLookController::OnKeyDown });

window.KeyUp({ this, &MoveLookController::OnKeyUp });

Mouse diperlakukan sedikit berbeda dari kontrol sentuhan meskipun menggunakan pointer. Untuk menyelaraskan dengan tata letak kontrol kami, MoveLookController memutar kamera setiap kali mouse dipindahkan, dan menembak saat tombol mouse kiri ditekan.

Ini ditangani dalam metode OnPointerPressed dari MoveLookController.

Dalam metode ini kita memeriksa untuk melihat jenis perangkat penunjuk apa yang digunakan dengan Windows::Devices::Input::PointerDeviceType enum. Jika game aktif dan PointerDeviceType bukan Touch, kami berasumsi itu adalah input mouse.

case MoveLookControllerState::Active:
    switch (pointerDeviceType)
    {
    case winrt::Windows::Devices::Input::PointerDeviceType::Touch:
        // Behavior for touch controls
        ...

    default:
        // Behavior for mouse controls
        bool rightButton = pointProperties.IsRightButtonPressed();
        bool leftButton = pointProperties.IsLeftButtonPressed();

        if (!m_autoFire && (!m_mouseLeftInUse && leftButton))
        {
            m_firePressed = true;
        }

        if (!m_mouseInUse)
        {
            m_mouseInUse = true;
            m_mouseLastPoint = position;
            m_mousePointerID = pointerID;
            m_mouseLeftInUse = leftButton;
            m_mouseRightInUse = rightButton;
            // These are for smoothing.
            m_lookLastDelta.x = m_lookLastDelta.y = 0;
        }
        break;
    }
    break;

Ketika pemutar berhenti menekan salah satu tombol mouse, peristiwa mouse CoreWindow::P ointerReleased dinaikkan, memanggil metode MoveLookController::OnPointerReleased , dan input selesai. Pada titik ini, bola akan berhenti menembak jika tombol mouse kiri sedang ditekan dan sekarang dilepaskan. Karena tampilan selalu diaktifkan, game terus menggunakan penunjuk mouse yang sama untuk melacak peristiwa tampilan yang sedang berlangsung.

case MoveLookControllerState::Active:
    // Touch points
    if (pointerID == m_movePointerID)
    {
        // Stop movement
        ...
    }
    else if (pointerID == m_lookPointerID)
    {
        // Stop look rotation
        ...
    }
    // Fire button has been released
    else if (pointerID == m_firePointerID)
    {
        // Stop firing
        ...
    }
    // Mouse point
    else if (pointerID == m_mousePointerID)
    {
        bool rightButton = pointProperties.IsRightButtonPressed();
        bool leftButton = pointProperties.IsLeftButtonPressed();

        // Mouse no longer in use so stop firing
        m_mouseInUse = false;

        // Don't clear the mouse pointer ID so that Move events still result in Look changes.
        // m_mousePointerID = 0;
        m_mouseLeftInUse = leftButton;
        m_mouseRightInUse = rightButton;
    }
    break;

Sekarang mari kita lihat jenis kontrol terakhir yang akan kita dukung: gamepad. Gamepad ditangani secara terpisah dari kontrol sentuhan dan mouse karena tidak menggunakan objek pointer. Karena itu, beberapa penanganan aktivitas dan metode baru perlu ditambahkan.

Menambahkan dukungan gamepad

Untuk game ini, dukungan gamepad ditambahkan oleh panggilan ke WINDOWS.Gaming.Input API. Set API ini menyediakan akses ke input pengontrol game seperti roda balap dan tongkat penerbangan.

Berikut ini akan menjadi kontrol gamepad kami.

Masukan pengguna Tindakan
Tongkat analog kiri Pindahkan pemutar
Tongkat analog kanan Mengubah rotasi (nada dan yaw) tampilan kamera
Pemicu yang tepat Menembakkan bola
Tombol Mulai/Menu Menjeda atau melanjutkan permainan

Dalam metode InitWindow, kami menambahkan dua peristiwa baru untuk menentukan apakah gamepad telah ditambahkan atau dihapus. Peristiwa ini memperbarui properti m_gamepadsChanged . Ini digunakan dalam metode UpdatePollingDevices untuk memeriksa apakah daftar gamepad yang diketahui telah berubah.

// Detect gamepad connection and disconnection events.
Gamepad::GamepadAdded({ this, &MoveLookController::OnGamepadAdded });

Gamepad::GamepadRemoved({ this, &MoveLookController::OnGamepadRemoved });

Catatan

Aplikasi UWP tidak dapat menerima input dari pengontrol game saat aplikasi tidak fokus.

Metode UpdatePollingDevices

Metode UpdatePollingDevices dari instans MoveLookController segera memeriksa untuk melihat apakah gamepad terhubung. Jika ada, kita akan mulai membaca statusnya dengan Gamepad.GetCurrentReading. Ini mengembalikan struct GamepadReading , memungkinkan kita untuk memeriksa tombol apa yang telah diklik atau thumbsticks dipindahkan.

Jika status permainan adalah WaitForInput, kita hanya mendengarkan tombol Start/Menu pengontrol sehingga game dapat dilanjutkan.

Jika Aktif, kami memeriksa input pengguna dan menentukan tindakan dalam game apa yang perlu terjadi. Misalnya, jika pengguna memindahkan tongkat analog kiri ke arah tertentu, ini memungkinkan game tahu bahwa kita perlu memindahkan pemain ke arah tongkat sedang dipindahkan. Pergerakan tongkat ke arah tertentu harus mendaftar lebih besar dari radius zona mati; jika tidak, tidak ada yang akan terjadi. Radius zona mati ini diperlukan untuk mencegah "hanyut," yaitu ketika pengontrol mengambil gerakan kecil dari jempol pemain saat berada di tongkat. Tanpa zona mati, kontrol dapat muncul terlalu sensitif terhadap pengguna.

Input thumbstick adalah antara -1 dan 1 untuk sumbu x dan y. Konstan berikut menentukan radius zona mati thumbstick.

#define THUMBSTICK_DEADZONE 0.25f

Dengan menggunakan variabel ini, kita kemudian akan mulai memproses input thumbstick yang dapat ditindak lanjuti. Pergerakan akan terjadi dengan nilai dari [-1, -.26] atau [.26, 1] pada salah satu sumbu.

dead zone for thumbsticks

Bagian metode UpdatePollingDevices ini menangani thumbstick kiri dan kanan. Setiap nilai X dan Y tongkat diperiksa untuk melihat apakah nilai tersebut berada di luar zona mati. Jika satu atau keduanya, kita akan memperbarui komponen yang sesuai. Misalnya, jika thumbstick kiri dipindahkan ke kiri di sepanjang sumbu X, kita akan menambahkan -1 ke komponen x dari vektor m_moveCommand . Vektor inilah yang akan digunakan untuk menggabungkan semua gerakan di semua perangkat dan nantinya akan digunakan untuk menghitung ke mana pemutar harus bergerak.

// Use the left thumbstick to control the eye point position
// (position of the player).

// Check if left thumbstick is outside of dead zone on x axis
if (reading.LeftThumbstickX > THUMBSTICK_DEADZONE ||
    reading.LeftThumbstickX < -THUMBSTICK_DEADZONE)
{
    // Get value of left thumbstick's position on x axis
    float x = static_cast<float>(reading.LeftThumbstickX);
    // Set the x of the move vector to 1 if the stick is being moved right.
    // Set to -1 if moved left. 
    m_moveCommand.x -= (x > 0) ? 1 : -1;
}

// Check if left thumbstick is outside of dead zone on y axis
if (reading.LeftThumbstickY > THUMBSTICK_DEADZONE ||
    reading.LeftThumbstickY < -THUMBSTICK_DEADZONE)
{
    // Get value of left thumbstick's position on y axis
    float y = static_cast<float>(reading.LeftThumbstickY);
    // Set the y of the move vector to 1 if the stick is being moved forward.
    // Set to -1 if moved backwards.
    m_moveCommand.y += (y > 0) ? 1 : -1;
}

Mirip dengan cara tongkat kiri mengontrol gerakan, tongkat kanan mengontrol rotasi kamera.

Perilaku tongkat jempol kanan selaras dengan perilaku gerakan mouse dalam pengaturan kontrol mouse dan keyboard kami. Jika tongkat berada di luar zona mati, kami menghitung perbedaan antara posisi penunjuk saat ini dan di mana pengguna sekarang mencoba melihat. Perubahan posisi penunjuk (pointerDelta) ini kemudian digunakan untuk memperbarui nada dan yaw rotasi kamera yang nantinya diterapkan dalam metode Pembaruan kami. Vektor pointerDelta mungkin terlihat akrab karena juga digunakan dalam metode MoveLookController::OnPointerMoved untuk melacak perubahan posisi pointer untuk input mouse dan sentuhan kami.

// Use the right thumbstick to control the look at position

XMFLOAT2 pointerDelta;

// Check if right thumbstick is outside of deadzone on x axis
if (reading.RightThumbstickX > THUMBSTICK_DEADZONE ||
    reading.RightThumbstickX < -THUMBSTICK_DEADZONE)
{
    float x = static_cast<float>(reading.RightThumbstickX);
    // Register the change in the pointer along the x axis
    pointerDelta.x = x * x * x;
}
// No actionable thumbstick movement. Register no change in pointer.
else
{
    pointerDelta.x = 0.0f;
}
// Check if right thumbstick is outside of deadzone on y axis
if (reading.RightThumbstickY > THUMBSTICK_DEADZONE ||
    reading.RightThumbstickY < -THUMBSTICK_DEADZONE)
{
    float y = static_cast<float>(reading.RightThumbstickY);
    // Register the change in the pointer along the y axis
    pointerDelta.y = y * y * y;
}
else
{
    pointerDelta.y = 0.0f;
}

XMFLOAT2 rotationDelta;
// Scale for control sensitivity.
rotationDelta.x = pointerDelta.x * 0.08f;
rotationDelta.y = pointerDelta.y * 0.08f;

// Update our orientation based on the command.
m_pitch += rotationDelta.y;
m_yaw += rotationDelta.x;

// Limit pitch to straight up or straight down.
m_pitch = __max(-XM_PI / 2.0f, m_pitch);
m_pitch = __min(+XM_PI / 2.0f, m_pitch);

Kontrol permainan tidak akan lengkap tanpa kemampuan untuk menembakkan bola!

Metode UpdatePollingDevices ini juga memeriksa apakah pemicu yang tepat sedang ditekan. Jika ya, properti m_firePressed kami dibalik ke true, memberi sinyal ke permainan bahwa bola harus mulai menembak.

if (reading.RightTrigger > TRIGGER_DEADZONE)
{
    if (!m_autoFire && !m_gamepadTriggerInUse)
    {
        m_firePressed = true;
    }

    m_gamepadTriggerInUse = true;
}
else
{
    m_gamepadTriggerInUse = false;
}

Metode Pembaruan

Untuk membungkus semuanya, mari kita gali lebih dalam metode Pembaruan . Metode ini menggabungkan gerakan atau rotasi apa pun yang dibuat pemain dengan input yang didukung untuk menghasilkan vektor kecepatan dan memperbarui nilai pitch dan yaw kami untuk diakses perulangan game kami.

Metode Pembaruan memulai berbagai hal dengan memanggil UpdatePollingDevices untuk memperbarui status pengontrol. Metode ini juga mengumpulkan input apa pun dari gamepad dan menambahkan gerakannya ke vektor m_moveCommand .

Dalam metode Pembaruan kami, kami kemudian melakukan pemeriksaan input berikut.

  • Jika pemutar menggunakan persegi panjang pengontrol pemindahan, kita kemudian akan menentukan perubahan posisi penunjuk dan menggunakannya untuk menghitung apakah pengguna telah memindahkan penunjuk keluar dari zona mati pengontrol. Jika di luar zona mati, properti vektor m_moveCommand kemudian diperbarui dengan nilai joystick virtual.
  • Jika salah satu input papan ketik gerakan ditekan, nilai 1.0f atau -1.0f ditambahkan dalam komponen vektor m_moveCommand yang sesuai—1.0f untuk maju, dan -1.0f untuk mundur.

Setelah semua input gerakan diperhitungkan, kami kemudian menjalankan vektor m_moveCommand melalui beberapa perhitungan untuk menghasilkan vektor baru yang mewakili arah pemain sehubungan dengan dunia game. Kami kemudian mengambil gerakan kami dalam kaitannya dengan dunia dan menerapkannya ke pemain sebagai kecepatan ke arah itu. Akhirnya kami mengatur ulang vektor m_moveCommand agar (0.0f, 0.0f, 0.0f) semuanya siap untuk bingkai game berikutnya.

void MoveLookController::Update()
{
    // Get any gamepad input and update state
    UpdatePollingDevices();

    if (m_moveInUse)
    {
        // Move control.
        XMFLOAT2 pointerDelta;

        pointerDelta.x = m_movePointerPosition.x - m_moveFirstDown.x;
        pointerDelta.y = m_movePointerPosition.y - m_moveFirstDown.y;

        // Figure out the command from the virtual joystick.
        XMFLOAT3 commandDirection = XMFLOAT3(0.0f, 0.0f, 0.0f);
        // Leave 32 pixel-wide dead spot for being still.
        if (fabsf(pointerDelta.x) > 16.0f)
            m_moveCommand.x -= pointerDelta.x / fabsf(pointerDelta.x);

        if (fabsf(pointerDelta.y) > 16.0f)
            m_moveCommand.y -= pointerDelta.y / fabsf(pointerDelta.y);
    }

    // Poll our state bits set by the keyboard input events.
    if (m_forward)
    {
        m_moveCommand.y += 1.0f;
    }
    if (m_back)
    {
        m_moveCommand.y -= 1.0f;
    }
    if (m_left)
    {
        m_moveCommand.x += 1.0f;
    }
    if (m_right)
    {
        m_moveCommand.x -= 1.0f;
    }
    if (m_up)
    {
        m_moveCommand.z += 1.0f;
    }
    if (m_down)
    {
        m_moveCommand.z -= 1.0f;
    }

    // Make sure that 45deg cases are not faster.
    if (fabsf(m_moveCommand.x) > 0.1f ||
        fabsf(m_moveCommand.y) > 0.1f ||
        fabsf(m_moveCommand.z) > 0.1f)
    {
        XMStoreFloat3(&m_moveCommand, XMVector3Normalize(XMLoadFloat3(&m_moveCommand)));
    }

    // Rotate command to align with our direction (world coordinates).
    XMFLOAT3 wCommand;
    wCommand.x = m_moveCommand.x * cosf(m_yaw) - m_moveCommand.y * sinf(m_yaw);
    wCommand.y = m_moveCommand.x * sinf(m_yaw) + m_moveCommand.y * cosf(m_yaw);
    wCommand.z = m_moveCommand.z;

    // Scale for sensitivity adjustment.
    // Our velocity is based on the command. Y is up.
    m_velocity.x = -wCommand.x * MoveLookConstants::MovementGain;
    m_velocity.z = wCommand.y * MoveLookConstants::MovementGain;
    m_velocity.y = wCommand.z * MoveLookConstants::MovementGain;

    // Clear movement input accumulator for use during next frame.
    m_moveCommand = XMFLOAT3(0.0f, 0.0f, 0.0f);
}

Langkah berikutnya

Sekarang setelah kita menambahkan kontrol kita, ada fitur lain yang perlu kita tambahkan untuk membuat permainan imersif: suara! Musik dan efek suara penting untuk permainan apa pun, jadi mari kita bahas menambahkan suara berikutnya.