Megosztás a következőn keresztül:


Vezérlők hozzáadása

Megjegyzés:

Ez a témakör a Egyszerű Univerzális Windows Platform (UWP) játék készítése DirectX-szel című oktatóanyag-sorozat része. A hivatkozás témaköre beállítja a sorozat kontextusát.

[ Windows 10-es UWP-alkalmazásokhoz frissítve. Windows 8.x-cikkek esetén lásd az archívumot ]

A jó Univerzális Windows Platform (UWP) játék számos felületet támogat. Lehetséges, hogy egy potenciális játékos a Windows 10-et olyan táblagépen használja, amelyen nincsenek fizikai gombok, egy pc van csatlakoztatva egy játékvezérlővel, vagy a legújabb asztali játékgép, amely nagy teljesítményű egérrel és játékbillentyűzettel rendelkezik. Játékunkban a vezérlők a MoveLookController osztályban vannak implementálva. Ez az osztály a bemenet mindhárom típusát (egér és billentyűzet, érintés és játékpad) egyetlen vezérlőbe összesíti. A végeredmény egy belső nézetű lövöldözős játék, amely a műfajban megszokott mozgás és nézet irányítási vezérléseket alkalmaz, és több eszközzel is működik.

Megjegyzés:

További információ a vezérlőkről a Játékok mozgás- és nézetvezérlése és a érintésvezérlésecímű részben található.

Célkitűzés

Jelenleg van egy játékunk, amely megjelenít, de nem tudjuk mozgatni a játékost, vagy lőni a célpontokra. Megvizsgáljuk, hogyan valósítja meg a játék az első személyű lövöldözős mozgás-nézet vezérlőket az UWP DirectX játékban az alábbi bemenettípusokhoz.

  • Egér és billentyűzet
  • Érintés
  • Játékpad

Megjegyzés:

Ha még nem töltötte le a minta legújabb játékkódját, lépjen tovább a Direct3D minta játék-ra. Ez a minta az UWP-szolgáltatásminták nagy gyűjteményének része. A minta letöltésére vonatkozó utasításokért lásd a Windows-fejlesztéshez készült mintaalkalmazásokat.

Gyakori vezérlési viselkedések

Az érintőképernyős vezérlők és az egér-billentyűzet vezérlők nagyon hasonló alapvető implementációval rendelkeznek. Az UWP-alkalmazásokban a mutató egyszerűen egy pont a képernyőn. Mozgathatja az egér csúsztatásával vagy az ujjának az érintőképernyőn való csúsztatásával. Ennek eredményeképpen regisztrálhat egyetlen eseménycsoportra, és nem kell aggódnia amiatt, hogy a játékos egérrel vagy érintőképernyővel mozgatja és lenyomja az egérmutatót.

A MoveLookController osztály inicializálása után négy mutatóspecifikus eseményre és egy egérspecifikus eseményre regisztrál:

Esemény Leírás
CoreWindow::P ointerPressed A bal vagy jobb egérgombot lenyomták (és megtartották), vagy megérintették az érintőfelületet.
CoreWindow::P ointerMoved Az egér át lett helyezve, vagy húzási művelet történt az érintőfelületen.
CoreWindow::P ointerReleased A bal egérgomb ki lett engedve, vagy az érintőfelülettel érintkező objektumot felemelték.
CoreWindow::P ointerExited Az egérmutató kimozdult a főablakból.
Windows::D evices::Input::MouseMoved Az egér elmozdult egy bizonyos távolságra. Vegye figyelembe, hogy csak az egér mozgási delta értékei érdekelnek minket, és nem az aktuális X-Y pozíciót.

Ezek az eseménykezelők azonnal elkezdik figyelni a felhasználói bemeneteket, amint a MoveLookController inicializálódik az alkalmazásablakban.

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

    ...
}

Az InitWindow teljes kódját a GitHubon tekintheti meg.

Annak meghatározásához, hogy a játéknak mikor kell figyelnie bizonyos bemeneteket, a MoveLookController osztály három vezérlőspecifikus állapotot tartalmaz, a vezérlő típusától függetlenül:

Állam Leírás
Nincs Ez a vezérlő inicializált állapota. Az összes bemenet figyelmen kívül lesz hagyva, mivel a játék nem számít semmilyen vezérlőbemenetre.
VárakozásBemeneretre A vezérlő arra vár, hogy a játékos nyugtázza a játék üzenetét egy bal egérkattintással, egy érintéses eseménysel, a játékpad menü gombján keresztül.
Aktív A vezérlő aktív játék módban van.

WaitForInput állapot és a játék szüneteltetése

A játék beírja a WaitForInput állapotot, amikor a játék szünetel. Ez akkor fordul elő, ha a játékos a mutatót a játék főablakán kívülre helyezi, vagy megnyomja a szüneteltetés gombot (a P billentyűt vagy a játékpadot indítása gombot). A MoveLookController regisztrálja a gombnyomást, és tájékoztatja a játék ciklust, amikor meghívja a IsPauseRequested metódust. Ezen a ponton, ha IsPauseRequested visszaadja igaz, a játék hurok ezután meghívja WaitForPress a MoveLookController, hogy helyezze át a vezérlőt a WaitForInput állapotba.

A WaitForInput állapotban a játék leállítja szinte az összes játékbemeneti esemény feldolgozását, amíg vissza nem tér az Aktív állapotba. A kivétel a szüneteltetés gomb, amelynek megnyomásával a játék visszalép az aktív állapotba. A szüneteltetés gombon kívül ahhoz, hogy a játék visszatérjen az Aktív állapothoz, a játékosnak ki kell választania egy menüelemet.

Az aktív állapot

Az Aktív állapotban a MoveLookController-példány feldolgoz minden engedélyezett bemeneti eszköz eseményeit, és értelmezi a játékos szándékait. Ennek eredményeképpen frissíti a játékos nézetének sebességét és nézési irányát, és megosztja a frissített adatokat a játékkal, miután a Update meghívásra kerül a játék ciklusából.

Az összes mutatóbemenet Aktív állapotban van nyomon követve, különböző mutatóazonosítókkal, amelyek különböző mutatóműveleteknek felelnek meg. Ha PointerPressed esemény érkezik, a MoveLookController lekéri az ablak által létrehozott mutatóazonosító-értéket. A mutatóazonosító egy adott típusú bemenetet jelöl. Egy több érintéses eszközön például egyszerre több különböző aktív bemenet is lehet. Az azonosítók segítségével nyomon követhető, hogy a játékos melyik bemenetet használja. Ha egy esemény az érintőképernyő mozgató téglalapjában van, a kurzorazonosító az áthelyezési téglalapban lévő összes mutatóesemény nyomon követéséhez van hozzárendelve. A tűz téglalap egyéb mutatóeseményei külön nyomon követhetők, külön mutatóazonosítóval.

Megjegyzés:

A játékvezérlő jobb hüvelykujjkarjának és az egérnek a bemenetei is rendelkeznek azonosítókkal, amelyeket külön kezelnek.

Miután a mutatóeseményeket leképezték egy adott játékműveletre, ideje frissíteni az adatokat, amelyeket a MoveLookController objektum megoszt a fő játékhurokkal.

Meghíváskor a Update metódus a mintajátékban feldolgozza a bemenetet, és frissíti a sebesség és a megjelenés irányváltozóit (m_velocity és m_lookdirection), amelyeket a játék hurok lekéri a nyilvános Velocity és LookDirection metódus meghívásával.

Megjegyzés:

A Update metódusról ezen a lapon talál további részleteket.

A játék hurok tesztelheti, hogy a játékos aktiválódik-e a IsFiring metódus meghívásával a MoveLookController példányon. A MoveLookController ellenőrzi, hogy a játékos megnyomta-e a tűz gombot a három bemeneti típus egyikén.

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

Most nézzük meg a három vezérlőtípus implementálását egy kicsit részletesebben.

Relatív egérvezérlők hozzáadása

Ha érzékeljük az egér mozgását, akkor ezt a mozgást szeretnénk felhasználni a kamera új emelkedési és elfordulási szögének meghatározásához. Ezt a relatív egérvezérlők implementálásával tesszük meg, ahol az egér által mozgatott relatív távolságot – a mozgás kezdete és a leállítás közötti különbséget – kezeljük, szemben a mozgás abszolút x-y képpont koordinátáinak rögzítésével.

Ehhez megszerezzük az X (a vízszintes mozgás) és az Y (a függőleges mozgás) koordináták változásait a MouseDelta::X és a MouseDelta::Y mezők vizsgálatával, amelyek a Windows::Device::Input::MouseEventArgs::MouseDelta argumentumobjektumban találhatók, amelyet a MouseMoved esemény ad vissza.

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

Érintéses támogatás hozzáadása

Az érintőképernyős vezérlők nagyszerűen támogatják a táblagépekkel rendelkező felhasználókat. Ez a játék úgy gyűjti össze az érintéses bemenetet, hogy a képernyő bizonyos területeit a játékon belüli műveletekhez igazítja. A játék érintéses bemenete három zónát használ.

mozgatás nézés érintéses elrendezés

Az alábbi parancsok összefoglalják az érintésvezérlés működését. Felhasználói bevitel | Művelet :------- | :-------- Téglalap áthelyezése | Az érintéses bemenet virtuális joystickmá alakul át, ahol a függőleges mozgást előre-hátra helyzet mozgássá, a vízszintes mozgást pedig bal/jobb pozíciós mozgássá alakítja. Lőj ki egy téglalapot | Lőj ki egy gömböt. Érintés a mozgási és lövési téglalapon kívül | Módosítsa a kameranézet forgatását (a dőlésszöget és az oldalirányú elfordulást).

A MoveLookController ellenőrzi a mutatóazonosítót az esemény helyének meghatározásához, és a következő műveletek egyikét hajtja végre:

  • Ha a PointerMoved esemény a mozgatás vagy tűz téglalapjában történt, frissítse a vezérlő mutatópozícióját.
  • Ha a PointerMoved esemény valahol a képernyő többi részén történt (nézetvezérlőként definiálva), számítsa ki a nézet irányvektorának dőlésszög és elfordulás változását.

Miután implementáltuk az érintésvezérlést, a Korábban a Direct2D használatával rajzolt téglalapok jelzik a játékosoknak, hogy hol vannak a mozgási, tűz- és megjelenési zónák.

érintésvezérlők

Most vessünk egy pillantást az egyes vezérlők implementálására.

Mozgás- és tűzvezérlő

A képernyő bal alsó negyedében található mozgató vezérlő téglalapot irányjelző padként használja a rendszer. A hüvelykujját balra és jobbra mozgatva ebben a területen a játékos balra és jobbra mozog, míg felfelé és lefelé mozgatva a kamerát előre és hátra mozgatja. A beállítás után a képernyő jobb alsó negyedében lévő tűzvezérlőre koppintva egy gömb aktiválódik.

A SetMoveRect és SetFireRect metódusok létrehozzák a bemeneti téglalapokat, két kétdimenziós vektor használatával megadva az egyes téglalapok bal felső és jobb alsó sarkának pozícióit a képernyőn.

A paraméterek ezután hozzá lesznek rendelve m_fireUpperLeft és m_fireLowerRight, amelyek segítenek megállapítani, hogy a felhasználó megérinti-e valamelyik téglalapot.

m_fireUpperLeft = upperLeft;
m_fireLowerRight = lowerRight;

Ha a képernyő átméretezve van, ezek a téglalapok a megfelelő méretre lesznek újrarajzoltak.

Most, hogy kikapcsoltuk a vezérlőinket, ideje megállapítani, hogy egy felhasználó mikor használja őket. Ehhez beállítunk néhány eseménykezelőt a MoveLookController::InitWindow metódusban arra az esetre, ha a felhasználó lenyomja, áthelyezi vagy felengedi a mutatót.

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

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

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

Először azt határozzuk meg, hogy mi történik, amikor a felhasználó először megérinti a mozgás vagy tűz téglalapjait az OnPointerPressed metódus használatával. Itt ellenőrizzük, hogy hol érintik a vezérlőt, és hogy van-e már mutató az adott vezérlőben. Ha ez az első ujj, amely megérinti az adott vezérlőt, a következőket tesszük.

  • Tárolja a touchdown helyét m_moveFirstDown vagy m_fireFirstDown 2D vektorként.
  • Rendelje hozzá a mutatóazonosítót m_movePointerID vagy m_firePointerID.
  • Állítsa be a megfelelő InUse jelzőt (m_moveInUse vagy m_fireInUse), mivel most már aktív mutatónk van ehhez a vezérlőhöz true.
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;
                ...
            }
        }
        ...

Most, hogy meghatároztuk, hogy a felhasználó egy mozdulathoz vagy egy tűzvezérléshez nyúl-e, kiderül, hogy a játékos bármilyen mozgást végez-e a megnyomott ujjával. A MoveLookController::OnPointerMoved metódussal ellenőrizzük, hogy mi mozgatta a mutatót, majd 2D vektorként tároljuk az új pozícióját.

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

Miután a felhasználó végrehajtotta a kézmozdulatait a vezérlőkben, felszabadítja a mutatót. A MoveLookController::OnPointerReleased metódussal meghatározzuk, hogy melyik mutató lett felszabadítva, és elvégezzük a visszaállítások sorozatát.

Ha elengedtük az áthelyezési vezérlőt, az alábbiakat hajtjuk végre.

  • Állítsa be a játékos sebességét 0-ra minden irányban, hogy megakadályozza a mozgását a játékban.
  • Váltson m_moveInUsefalse, mivel a felhasználó már nem érinti a mozgásvezérlőt.
  • Állítsa be az áthelyezési mutató azonosítóját 0-ra, mivel már nincs több mutató az áthelyezésvezérlőben.
if (pointerID == m_movePointerID)
{
    // Stop on release.
    m_velocity = XMFLOAT3(0, 0, 0);
    m_moveInUse = false;
    m_movePointerID = 0;
}

A tűzvezérlés esetében, ha már felszabadult, mindössze annyit teszünk, hogy a m_fireInUse jelzőt false értékre állítjuk, és a tűzmutató azonosítóját 0 értékre változtatjuk, mivel már nincs mutató a tűzvezérlésben.

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

Nézetvezérlő

Az érintőeszközök mutatóeseményeit a képernyő nem használt régióihoz úgy kezeljük, mint a nézetirányító. Az ujját a zóna körül csúsztatva megváltozik a játékos kamerájának dőlése és elfordulása.

Ha a MoveLookController::OnPointerPressed eseményt egy érintőeszközön állítja be ebben a régióban, és a játék állapota Aktív, akkor egy mutatóazonosító van hozzárendelve.

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

Itt a MoveLookController rendeli hozzá az eseményt aktiváló mutató mutatóazonosítóját egy adott változóhoz, amely megfelel a keresési régiónak. Ha a keresési régióban érintés történik, a m_lookPointerID változó az eseményt aktiváló mutatóazonosítóra van állítva. A logikai változó (m_lookInUse) azt is jelzi, hogy a vezérlő még nem lett felszabadítva.

Most nézzük meg, hogyan kezeli a mintajáték a PointerMoved érintőképernyős eseményt.

A MoveLookController::OnPointerMoved metóduson belül ellenőrizzük, hogy milyen típusú mutatóazonosító lett hozzárendelve az eseményhez. Ha m_lookPointerID, kiszámítjuk a mutató pozíciójának változását. Ezt a különbözetet használva kiszámítjuk, hogy a forgatás mennyiben változzon. Végül eljutottunk odáig, hogy frissíthetjük a m_pitch és a m_yaw elemeket, amelyek a játékban a játékos forgásának megváltoztatására szolgálnak.

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

Az utolsó dolog, amit megvizsgálunk, az, hogy a mintajáték hogyan kezeli a PointerReleased érintőképernyős eseményt. Miután a felhasználó befejezte az érintéses kézmozdulatot, és eltávolította az ujját a képernyőről, MoveLookController::OnPointerReleased indul el. Ha az PointerReleased eseményt aktiváló mutató azonosítója a korábban rögzített mozgásmutató azonosítója, akkor a MoveLookController beállítja a sebességet 0-ra, mert a játékos abbahagyta a nézési területet érintését.

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

Egér- és billentyűzettámogatás hozzáadása

Ez a játék a következő vezérlő elrendezéssel rendelkezik a billentyűzethez és az egérhez.

Felhasználói bevitel Tevékenység
W Játékos előre mozgatása
Egy Játékos mozgatása balra
S Játékos hátra mozgatása
D Játékos áthelyezése jobbra
X Nézet feljebb
Szóköz Nézet lefelé mozgatása
P A játék szüneteltetése
Egér mozgása A kamerakép forgatásának (a dőlés és a fordulás) módosítása
Bal egérgomb Lőj ki egy gömböt

A billentyűzet használatához a mintajáték két új eseményt regisztrál, CoreWindow::KeyUp és CoreWindow::KeyDown, a MoveLookController::InitWindow metóduson belül. Ezek az események kezelik egy billentyű lenyomását és felengedését.

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

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

Az egér kezelése egy kicsit más, mint az érintésvezérlés, annak ellenére, hogy egy mutatót használ. A vezérlő elrendezéséhez igazodva a MoveLookController az egér áthelyezésekor elforgatja a kamerát, és a bal egérgomb lenyomásakor aktiválódik.

Ezt a MoveLookControllerOnPointerPressed metódusa kezeli.

Ebben a metódusban ellenőrizzük, hogy milyen típusú mutatóeszközt használunk a Windows::Devices::Input::PointerDeviceType enumeráláshoz. Ha a játék Aktív, és a PointerDeviceType nem Touch, akkor feltételezzük, hogy egérhasználat történik.

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;

Amikor a játékos abbahagyja az egérgombok egyikének nyomva tartását, a CoreWindow::PointerReleased egéresemény váltódik ki, amely meghívja a MoveLookController::OnPointerReleased metódust, és a bemenet lezárul. Ezen a ponton a gömbök leállnak, ha a bal egérgombot lenyomták, és most elengedik. Mivel a nézési funkció mindig engedélyezve van, a játék továbbra is ugyanazt az egérmutatót használja a folyamatban lévő nézési események nyomon követéséhez.

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;

Most nézzük meg az utolsó vezérlőtípust, amit támogatunk: gamepadek. A gamepadeket az érintés- és egérvezérlőktől külön kezelik, mivel nem használják a mutatóobjektumot. Emiatt néhány új eseménykezelőt és metódust kell hozzáadni.

Gamepad-támogatás hozzáadása

Ebben a játékban a Windows.Gaming.Input API-k hívásai hozzáadják a gamepadek támogatását. Ez az API-készlet hozzáférést biztosít a játékvezérlő bemeneteihez, például a versenykerékhez és a rúdhoz.

A következők lesznek a játékpad vezérlői.

Felhasználói bevitel Tevékenység
Bal analóg kar Játékos mozgatása
Jobb analóg kar A kamerakép forgatásának (a dőlés és a fordulás) módosítása
Jobb oldali eseményindító Lőj ki egy gömböt
Start/Menü gomb A játék szüneteltetése vagy folytatása

Az InitWindow metódusban két új eseményt adunk hozzá annak megállapítására, hogy egy gamepad hozzá lett-e adva vagy el lett-e távolítva. Ezek az események frissítik a m_gamepadsChanged tulajdonságot. Ez a UpdatePollingDevices metódusban használatos annak ellenőrzésére, hogy az ismert játékpadok listája megváltozott-e.

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

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

Megjegyzés:

Az UWP-alkalmazások nem fogadhatnak bemenetet egy játékvezérlőtől, miközben az alkalmazás nincs a fókuszban.

Az UpdatePollingDevices metódus

A MoveLookController példány UpdatePollingDevices metódusa azonnal ellenőrzi, hogy csatlakoztatva van-e egy játékpad. Ha igen, elkezdjük olvasni az állapotát a Gamepad.GetCurrentReading használatával. Ez a GamepadReading szerkezetét adja vissza, így ellenőrizheti, hogy milyen gombokra kattintottak, vagy milyen ujjlenyomatokat mozgattak.

Ha a játék állapota WaitForInput, csak a vezérlő Start/Menu gombját figyeljük, hogy a játék folytatható legyen.

Ha Aktív, ellenőrizzük a felhasználó bemenetét, és meghatározzuk, hogy milyen játékon belüli műveletnek kell történnie. Például, ha a felhasználó a bal analóg kart egy adott irányba mozgatta, ez jelzi a játéknak, hogy a játékos a kar mozdításának irányába mozduljon. A bot meghatározott irányban történő mozgásának nagyobbnak kell lennie, mint a holt zónasugara; ellenkező esetben semmi sem fog történni. Ez a holt zóna sugara szükséges a "sodródás" megelőzéséhez, ami akkor fordul elő, amikor a vezérlő érzékeli a játékos hüvelykujjának apró mozdulatait, miközben az a boton pihen. Holt zónák nélkül a vezérlők túl érzékenyek lehetnek a felhasználóra.

A hüvelykujj bemenete az x és az y tengely -1 és 1 között van. Az alábbi állandó a hüvelykujj-holt zóna sugarát határozza meg.

#define THUMBSTICK_DEADZONE 0.25f

Ezt a változót használva megkezdjük a használható hüvelykujj bemenetének feldolgozását. A mozgás bármelyik tengelyen a [-1, -.26] vagy [.26, 1] értéktartományban történik.

holt zóna az analóg karokhoz

Ez a UpdatePollingDevices metódus a bal és a jobb hüvelykujjat kezeli. Minden bot X és Y értékeit ellenőrzi a rendszer, hogy a holt zónán kívül vannak-e. Ha valamelyik vagy mindkettő, frissítjük a megfelelő összetevőt. Ha például a bal oldali hüvelykujj az X tengely mentén balra kerül, -1 a m_moveCommand vektor x összetevőjéhez adunk hozzá. Ez a vektor az összes mozgás összesítésére szolgál az összes eszközön, és később a játékos mozgásának kiszámítására szolgál.

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

A bal oldali bot mozgásához hasonlóan a jobb oldali bot vezérli a kamera forgatását.

A jobb hüvelykujj bot viselkedése igazodik az egér mozgásának viselkedéséhez az egér és a billentyűzetvezérlő beállításában. Ha a bot kívül esik a holt zónán, kiszámítjuk az aktuális mutatópozíció és a felhasználó által keresendő hely közötti különbséget. Ez a változás a mutató pozíciójában (pointerDelta) ezután a kameraforgatás dőlésszögének és forgásszögének frissítésére szolgál, amelyet később az Update metódusban alkalmazunk. A pointerDelta vektor ismerős lehet, mert a MoveLookController::OnPointerMoved metódusban is használják az egér- és érintésvezérlések mutatópozíció-változásának követésére.

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

A játék vezérlője nem lenne teljes anélkül, hogy képes lenne gömböket kilőni!

Ez a UpdatePollingDevices metódus azt is ellenőrzi, hogy a megfelelő eseményindító van-e lenyomva. Ha igen, a m_firePressed tulajdonságunk igazra vált, jelezve a játéknak, hogy a gömböknek el kell kezdeniük a tüzelést.

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

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

Az Update metódus

Összefoglalásként, ássunk mélyebbre a Update módszerben. Ez a módszer egyesíti azokat a mozgásokat vagy forgásokat, amelyeket a játékos bármilyen támogatott bemenettel végzett, hogy létrehozzon egy sebességvektort, és frissítse a játéklépés során elérhető dobás és elfordulás értékeket.

A Update metódus elindítja a folyamatot a UpdatePollingDevices meghívásával, hogy frissítse a vezérlő állapotát. Ez a módszer a játékpad összes bemenetét is összegyűjti, és a mozgásokat hozzáadja a m_moveCommand vektorhoz.

A Update metódusban a következő bemeneti ellenőrzéseket hajtjuk végre.

  • Ha a játékos az áthelyezésvezérlő téglalapját használja, meghatározzuk a mutató pozíciójának változását, és ezzel kiszámítjuk, hogy a felhasználó áthelyezte-e a mutatót a vezérlő holtzónájából. Ha a holt zónán kívül van, a m_moveCommand vektortulajdonság ekkor frissül a virtuális joystick értékével.
  • Ha a mozgásbillentyűzet bármelyik bemenete be van nyomva, a 1.0f vektor megfelelő összetevőjében -1.0f vagy értéket ad hozzá–1.0f előre és -1.0f visszafelé.

Miután az összes mozgási bemenetet figyelembe vettük, a m_moveCommand vektort néhány számítással futtatjuk, hogy létrehozzuk az új vektort, amely a játékos irányát jelöli a játék világa szempontjából. Ezután a világhoz viszonyítva fogjuk a mozgásainkat, és a játékosra alkalmazzuk őket sebességként ebben az irányban. Végül visszaállítjuk a m_moveCommand vektort (0.0f, 0.0f, 0.0f), hogy minden készen álljon a következő játékkeretre.

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

Következő lépések

Most, hogy hozzáadtuk a vezérlőket, egy másik funkciót kell hozzáadnunk egy modern játék létrehozásához: hang! A zene és a hangeffektusok minden játékban fontosak, ezért beszéljük meg hang hozzáadását következő lépésként.