Sdílet prostřednictvím


Přidání ovládacích prvků

Poznámka:

Toto téma je součástí Vytvoření jednoduché hry pro Univerzální platformu Windows (UPW) pomocí série kurzů DirectX. Téma na tomto odkazu nastaví kontext pro řadu.

[ Aktualizováno pro aplikace pro UPW ve Windows 10. Články o windows 8.x najdete v archivu ]

Dobrá hra univerzální platformy Windows (UPW) podporuje širokou škálu rozhraní. Potenciální hráč může mít Windows 10 na tabletu bez fyzických tlačítek, počítače s připojeným herním ovladačem nebo nejnovější desktopové herní soupravy s vysoce výkonnou myší a herní klávesnicí. V naší hře jsou ovládací prvky implementovány ve třídě MoveLookController. Tato třída agreguje všechny tři typy vstupu (myš a klávesnice, dotykové ovládání a gamepad) do jednoho ovladače. Konečným výsledkem je střílečka z pohledu první osoby, která používá standardní ovládání pohybu a pohledu, které funguje s více zařízeními.

Poznámka:

Další informace o ovládacích prvcích najdete v tématu Pohybové a pohledové ovládací prvky pro hry a Dotykové ovládací prvky pro hry.

Účel

V tuto chvíli máme hru, která se vykreslí, ale nemůžeme pohybovat naším hráčem ani střílet na cíle. Podíváme se na to, jak naše hra implementuje ovládání pohybu a pohledu z pohledu první osoby pro následující typy vstupu v naší UWP DirectX hře.

  • Myš a klávesnice
  • Dotykové ovládání
  • Herní ovladač

Poznámka:

Pokud jste si nestáhli nejnovější kód hry pro tuto ukázku, přejděte na ukázkovou hru Direct3D. Tato ukázka je součástí velké kolekce ukázek funkcí UPW. Pokyny ke stažení ukázky najdete v tématu Ukázkové aplikace pro vývoj ve Windows.

Běžné chování ovládacích prvků

Dotykové ovládání a ovládání myši/klávesnice mají velmi podobnou základní implementaci. V aplikaci pro UPW je ukazatel jednoduše bodem na obrazovce. Můžete ho přesunout posunutím myši nebo posunutím prstu na dotykové obrazovce. V důsledku toho můžete zaregistrovat jednu sadu událostí a nemusíte se starat o to, jestli hráč používá myš nebo dotykovou obrazovku k pohybu a stisknutí ukazatele.

Když Je inicializována třída MoveLookController v ukázkové hře, zaregistruje se pro čtyři události specifické pro ukazatel a jednu událost specifickou pro myš:

Událost Popis
CoreWindow::PointerPressed Tlačítko myši vlevo nebo vpravo bylo stisknuto (a přidrženo) nebo byl dotknut dotykový povrch.
CoreWindow::PointerMoved Myš se přesunula nebo byla na dotykovém povrchu provedena akce přetažení.
CoreWindow::P ointerReleased Tlačítko levého tlačítka myši bylo uvolněno nebo byl zvednut objekt kontaktující dotykovou plochu.
CoreWindow::PointerExited Ukazatel se přesunul z hlavního okna.
Windows::Devices::Input::MouseMoved Myš přesunula určitou vzdálenost. Mějte na paměti, že nás zajímají pouze hodnoty změny pohybu myši, nikoliv aktuální pozice X-Y.

Tyto obslužné rutiny událostí jsou nastaveny tak, aby začaly naslouchat uživatelským vstupům, jakmile je MoveLookController inicializován v okně aplikace.

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

    ...
}

Kompletní kód pro InitWindow můžete vidět na GitHubu.

Chcete-li určit, kdy hra by měla naslouchat pro určitý vstup, MoveLookController třída má tři stavy specifické pro kontroleru bez ohledu na typ kontroleru:

Stát Popis
Nic Toto je inicializovaný stav kontroleru. Veškerý vstup je ignorován, protože hra neočekává žádný vstup kontroleru.
WaitForInput Ovladač čeká na to, aby hráč potvrdil zprávu ze hry buď pomocí levého kliknutí myší, dotykové události, nebo tlačítka nabídky na herním ovladači.
Aktivní Ovladač je v aktivním herním režimu.

Stav WaitForInput a pozastavení hry

Hra vstoupí do stavu WaitForInput, když je pozastavena. K tomu dochází, když hráč přesune ukazatel mimo hlavní okno hry nebo stiskne tlačítko pozastavení (klávesa P nebo tlačítko Start herního panelu). MoveLookController zaregistruje stisknutí a informuje herní smyčku, když je zavolána metoda IsPauseRequested. Pokud IsPauseRequested vrátí hodnotu true, herní smyčka poté zavolá WaitForPress na MoveLookController, aby přesunula kontroler do stavu WaitForInput.

Jakmile se hra ocitne ve stavu WaitForInput, přestane zpracovávat téměř všechny herní vstupní události, dokud se nevrátí do stavu Aktivní. Výjimkou je tlačítko pozastavení, které stisknutou klávesou způsobí, že se hra vrátí do aktivního stavu. Mimo použití tlačítka pro pozastavení, musí hráč vybrat položku nabídky, aby se hra vrátila do Aktivního stavu.

Aktivní stav

Během stavu Aktivní zpracovává instance MoveLookController události ze všech povolených vstupních zařízení a interpretuje záměry přehrávače. V důsledku toho aktualizuje rychlost a směr pohledu hráče a sdílí aktualizovaná data se hrou poté, co je Update volán ze smyčky hry.

Veškerý vstup z ukazatele je sledován ve stavu Aktivní, přičemž různá ID ukazatele odpovídají různým akcím ukazatele. Při přijetí události PointerPressed získá MoveLookController hodnotu ID ukazatele, kterou vytvořilo okno. ID ukazatele představuje konkrétní typ vstupu. Například na vícedotykovém zařízení může současně existovat několik různých aktivních vstupů. ID slouží ke sledování toho, jaký vstup hráč používá. Pokud je jedna událost v obdélníku pohybu dotykové obrazovky, přiřadí se ID ukazatele ke sledování událostí ukazatele v obdélníku pohybu. Další události ukazatele v obdélníku událostí jsou sledovány odděleně s odděleným ID ukazatele.

Poznámka:

Vstup z myši a pravého palce herního panelu mají také ID, která se zpracovávají samostatně.

Po namapování událostí ukazatele na konkrétní akci hry je čas aktualizovat data, která objekt MoveLookController sdílí s hlavní herní smyčkou.

Při volání metody Update v ukázkové hře zpracovává vstup a aktualizuje proměnné rychlosti a směru pohledu (m_velocity a m_lookdirection), které pak herní smyčka načte voláním veřejných metod Velocity a LookDirection.

Poznámka:

Další podrobnosti o metodě aktualizace najdete později na této stránce.

Herní smyčka může ověřit, zda hráč střílí, zavoláním metody IsFiring na instanci MoveLookController. MoveLookController zkontroluje, jestli hráč stiskl tlačítko fire na jednom ze tří typů vstupu.

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

Teď se podrobněji podíváme na implementaci každého ze tří typů ovládacích prvků.

Přidání relativních ovládacích prvků myši

Pokud je detekován pohyb myši, chceme tento pohyb použít k určení nového sklonu a úhlu natočení kamery. Děláme to implementací relativních ovládacích prvků myši, kde zpracováváme relativní vzdálenost, kterou myš přesunula – rozdíl mezi začátkem pohybu a zastavením – místo zaznamenávání absolutních souřadnic x-y pixelů pohybu.

Chcete-li to udělat, získáme změny v souřadnicích X (vodorovný pohyb) a Y (svislý pohyb) prozkoumáním polí MouseDelta::X a MouseDelta::Y na argumentovém objektu Windows::Device::Input::MouseEventArgs::MouseDelta, který je vrácený událostí 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;
    }
}

Přidání podpory dotykového ovládání

Dotykové ovládání je skvělé pro podporu uživatelů s tablety. Tato hra shromažďuje dotykové vstupy tak, že určité oblasti obrazovky slouží pro konkrétní akce ve hře. Dotykový vstup této hry používá tři zóny.

přesunout rozložení dotykového ovládání

Následující příkazy shrnují chování dotykového ovládání. Uživatelský vstup | Akce :------- | :-------- Přesunout obdélník | Dotykový vstup se převede na virtuální joystick, kde se svislý pohyb přeloží do pohybu dopředu/dozadu a vodorovný pohyb se přeloží do pohybu doleva/doprava. Vystřelit obdélník | Vystřelit kouli. Dotkněte se mimo oblast pohybu a střelby | Změňte otočení (náklon a stáčení) zobrazení kamery.

MoveLookController zkontroluje ID ukazatele, aby určil, kde došlo k události, a provede jednu z následujících akcí:

  • Pokud došlo k události PointerMoved v obdélníku pro pohyb nebo střelbu, aktualizujte pozici ukazatele pro ovladač.
  • Pokud došlo k události PointerMoved někde na zbytku obrazovky (definovaném jako ovládací prvky pohledu), vypočítejte změnu v náklonu a otočení směrového vektoru pohledu.

Po implementaci dotykových ovládacích prvků budou obdélníky, které jsme nakreslili dříve pomocí Direct2D, indikovat hráčům, kde se pohyb, oheň a vzhled zón nacházejí.

dotykové ovládání

Teď se podíváme na to, jak implementujeme každý ovládací prvek.

Pohybový a požární kontroler

Obdélník ovladače pro přesunutí v levém dolním kvadrantu obrazovky se používá jako směrová podložka. Posunutím palce doleva a doprava v tomto prostoru se hráč přesune doleva a doprava, zatímco nahoru a dolů přesune kameru dopředu a dozadu. Po nastavení tohoto nastavení se po klepnutí na ovladač ohně v pravém dolním kvadrantu obrazovky aktivuje koule.

SetMoveRect a SetFireRect metody vytvářejí naše vstupní obdélníky, které přijímají dva 2D vektory k určení pozic levého horního a pravého dolního rohu jednotlivých obdélníků na obrazovce.

Parametry se pak přiřadí k m_fireUpperLeft a m_fireLowerRight, které nám pomůžou určit, jestli se uživatel dotkne jednoho z obdélníků.

m_fireUpperLeft = upperLeft;
m_fireLowerRight = lowerRight;

Pokud se změní velikost obrazovky, překreslí se tyto obdélníky na odpovídající velikost.

Teď, když jsme vypnuli ovládací prvky, je čas určit, kdy je uživatel skutečně používá. Za tímto účelem jsme nastavili některé obslužné rutiny událostí v metodě MoveLookController::InitWindow pro okamžiky, kdy uživatel stiskne, přesune nebo uvolní svůj ukazatel.

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

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

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

Nejprve určíme, co se stane, když uživatel poprvé stiskne v rámci přesunutí nebo aktivuje obdélníky pomocí metody OnPointerPressed. Tady zkontrolujeme, kde se dotkne ovládacího prvku a jestli je ukazatel již v daném ovladači. Pokud se jedná o první prst, který se dotkne konkrétního ovládacího prvku, provedeme následující kroky.

  • Uložte polohu přistání v m_moveFirstDown nebo m_fireFirstDown jako 2D vektor.
  • Přiřaďte ID ukazatele k m_movePointerID nebo k m_firePointerID.
  • Nastavte správný příznak InUse (m_moveInUse nebo m_fireInUse) na hodnotu true, protože teď máme pro tento ovládací prvek aktivní ukazatel.
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;
                ...
            }
        }
        ...

Teď, když jsme zjistili, jestli se uživatel dotýká ovládání pohybu nebo střelby, zjišťujeme, zda hráč provádí pohyby se stisknutým prstem. Pomocí metody MoveLookController::OnPointerMoved zkontrolujeme, jaký ukazatel se přesunul, a pak uložíme jeho novou pozici jako 2D vektor.

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

Jakmile uživatel provede gesta v ovládacích prvcích, uvolní ukazatel. Pomocí metody MoveLookController::OnPointerReleased určíme, který ukazatel byl vydán, a provedeme řadu resetů.

Pokud je ovládací prvek přesunutí uvolněný, provedeme následující kroky.

  • Nastavte rychlost hráče na hodnotu 0 ve všech směrech, aby jim bylo zabráněno v pohybu ve hře.
  • Změňte m_moveInUse na false, protože uživatel již nedotýká ovladače pohybu.
  • Nastavte ID přesunutí ukazatele na 0, protože už v kontroleru přesunutí není ukazatel.
if (pointerID == m_movePointerID)
{
    // Stop on release.
    m_velocity = XMFLOAT3(0, 0, 0);
    m_moveInUse = false;
    m_movePointerID = 0;
}

Pokud bylo řízení požáru uvolněno, přepneme příznak m_fireInUse na false a ukazatel ID požáru na 0, protože už není ukazatel v řízení požáru.

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

Kontroler vzhledu

Události ukazatele dotykového zařízení v nepoužívaných oblastech obrazovky považujeme za ovladač pohledu. Pohybem prstu v této oblasti se změní náklon a úhel natočení kamery hráče.

Pokud je na dotykovém zařízení v této oblasti vyvolána událost MoveLookController::OnPointerPressed a stav hry je nastaven na Aktivní, je této události přiřazeno ID ukazatele.

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

V tomto MoveLookController přiřadí ID ukazatele pro ukazatel, který aktivoval událost, konkrétní proměnné, která odpovídá zóně pohledu. V případě dotyku v oblasti vzhledu je proměnná m_lookPointerID nastavena na ID ukazatele, které událost aktivovalo. Logická proměnná, m_lookInUse, je také nastavena, aby označovala, že ovladač ještě nebyl uvolněn.

Nyní se podívejme, jak ukázková hra zpracovává událost dotykové obrazovky PointerMoved.

V rámci Metody MoveLookController::OnPointerMoved zkontrolujeme, zda bylo k události přiřazeno ID ukazatele. Pokud je m_lookPointerID, vypočítáme změnu pozice ukazatele. Tuto rozdílovou hodnotu pak použijeme k výpočtu, o kolik se má změnit otočení. Nakonec se nacházíme v bodě, kdy můžeme aktualizovat m_pitch a m_yaw, tak aby byly použity v rámci hry, aby změnily rotaci hráče.

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

Poslední část, na kterou se podíváme, je způsob, jak ukázková hra zpracovává událost dotykové obrazovky PointerReleased. Jakmile uživatel dokončí dotykové gesto a odebere prst z obrazovky, spustí se MoveLookController::OnPointerReleased . Pokud ID ukazatele, který aktivoval událost PointerReleased, je ID toho dříve zaznamenaného ukazatele přesunutí, MoveLookController nastaví rychlost na 0, protože hráč přestal se dotýkat oblasti vzhledu.

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

Přidání podpory myši a klávesnice

Tato hra má následující rozložení ovládacího prvku pro klávesnici a myš.

Uživatelský vstup Činnost
W Přesunout přehrávač dopředu
A Přesunout přehrávač doleva
S Přesunout přehrávač dozadu
D Přesunout přehrávač doprava
X Přesunout zobrazení nahoru
Mezerník Přesunout zobrazení dolů
P Pozastavení hry
Pohyb myši Změňte otočení (náklon a klopení) pohledu kamery
Levé tlačítko myši Oheň koule

Pro použití klávesnice zaregistruje ukázková hra v metodě MoveLookController::InitWindow dvě nové události, CoreWindow::KeyUp a CoreWindow::KeyDown. Tyto události zpracovávají stisknutí a uvolnění klávesy.

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

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

S myší se zachází trochu jinak než s dotykovými ovládacími prvky, i když používá ukazatel. Pro zarovnání s rozložením ovládacích prvků, MoveLookController otáčí kameru při každém pohybu myši a pálí při stisknutí levého tlačítka myši.

V metodě OnPointerPressed objektu MoveLookControllerse to zpracovává.

V této metodě kontrolujeme, jaký typ ukazovacího zařízení se používá s výčtem Windows::Devices::Input::PointerDeviceType. Pokud je hra aktivní a PointerDeviceType není touch, předpokládáme, že se jedná o vstup myši.

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;

Když hráč přestane stiskávat jedno z tlačítek myši, je vyvolána událost myši CoreWindow::PointerReleased, která volá metodu MoveLookController::OnPointerReleased, a tím je vstup dokončen. V tomto okamžiku se koule přestanou zapalovat, pokud bylo stisknuto levé tlačítko myši a je nyní uvolněno. Protože je pohled vždy povolený, hra nadále používá stejný ukazatel myši ke sledování probíhajících pohledových událostí.

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;

Teď se podíváme na poslední typ ovládacího prvku, který budeme podporovat: gamepady. Gamepady se zpracovávají odděleně od ovládacích prvků dotykového ovládání a myši, protože nepoužívají objekt ukazatele. Z tohoto důvodu bude potřeba přidat několik nových obslužných rutin událostí a metod.

Přidání podpory herního panelu

Pro tuto hru je podpora herního panelu přidána voláním rozhraní API windows.Gaming.Input . Tato sada rozhraní API poskytuje přístup ke vstupům herního ovladače, jako jsou závodní kola a letové sticky.

Následuje ovládání herního panelu.

Uživatelský vstup Činnost
Levá analogová páčka Přesunout přehrávač
Pravá analogová páčka Změňte otočení (náklon a klopení) pohledu kamery
Pravá spoušť Oheň koule
Tlačítko Start/Nabídka Pozastavení nebo obnovení hry

V metodě InitWindow přidáme dvě nové události, abychom zjistili, jestli byl gamepad přidán nebo odstraněn. Tyto události aktualizují vlastnost m_gamepadsChanged . Používá se v metodě UpdatePollingDevices ke kontrole, jestli se změnil seznam známých gamepadů.

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

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

Poznámka:

Aplikace pro UWP nemohou přijímat vstup z herního ovladače, zatímco aplikace není aktivní.

Metoda AktualizovatZařízeníProAnketování

Metoda UpdatePollingDevices instance MoveLookController okamžitě zkontroluje, jestli je gamepad připojený. Pokud tomu tak je, začneme číst jeho stav pomocí Gamepad.GetCurrentReading. Tím se vrátí GamepadReading struktura, což nám umožňuje zkontrolovat, na jaká tlačítka bylo kliknuto nebo jak byl pohnutý analogový stick.

Pokud je stav hry WaitForInput, čekáme pouze na stisknutí tlačítka Start/Menu ovladače, pro to, aby bylo možné hru obnovit.

Pokud je aktivní, zkontrolujeme vstup uživatele a určíme, co se má stát ve hře. Pokud například uživatel přesunul levou analogovou hůl v určitém směru, dá to hře vědět, že potřebujeme přesunout hráče ve směru, ve které se hůl přesouvá. Pohyb hůl v určitém směru se musí zaregistrovat jako větší než poloměr mrtvé zóny; jinak se nic nestane. Tento poloměr mrtvé zóny je nezbytný k tomu, aby se zabránilo „driftování“, což nastává, když ovladač zaznamenává drobné pohyby palce hráče, když spočívá na páčce. Bez mrtvých zón se ovládací prvky můžou uživateli zobrazovat příliš citlivé.

Vstup palcového ovladače je mezi -1 a 1 pro osy x i y. Následující konstanta určuje poloměr mrtvé zóny kryptografického znaménka.

#define THUMBSTICK_DEADZONE 0.25f

Pomocí této proměnné pak začneme zpracovávat vstupy kryptografického znaménka, které se dají zpracovat. Pohyb by nastal s hodnotou z [-1, -.26] nebo [.26, 1] na obou osách.

mrtvá zóna pro analogové páčky

Tato část UpdatePollingDevices metoda zpracovává levý a pravý palec. Hodnoty X a Y jednotlivých pák se kontrolují, aby se zjistilo, zda nejsou mimo mrtvou zónu. Pokud ano, aktualizujeme odpovídající komponentu. Pokud je například levý palec posunut doleva podél osy X, přidáme -1 ke komponentě x vektoru m_moveCommand . Tento vektor se použije k agregaci všech pohybu na všech zařízeních a později se použije k výpočtu místa, kam se má hráč pohybovat.

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

Podobně jako levý joystick ovládá pohyb, pravý joystick řídí otáčení kamery.

Chování pravé páčky odpovídá chování pohybu myši v nastavení ovládání pomocí myši a klávesnice. Pokud je ovládací páčka mimo mrtvou zónu, vypočítáme rozdíl mezi aktuální polohou ukazatele a místem, kam se uživatel nyní snaží dívat. Tato změna pozice ukazatele (ukazatelDelta) se pak použije k aktualizaci náklonu a rotace kamery, které se později použijí v naší metodě Update. Vektor pointerDelta může vypadat dobře, protože se používá také v metodě MoveLookController::OnPointerMoved pro sledování změn v poloze ukazatele pro naše vstupy myši a dotykového ovládání.

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

Ovládání hry by nebylo úplné bez schopnosti střílet koule!

Tato metoda UpdatePollingDevices také zkontroluje, jestli je stisknuta pravá spoušť. Pokud ano, naše vlastnost m_firePressed je nastavena na true, což signalizuje hře, že koule by měly začít střílet.

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

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

Metoda aktualizace

Na závěr se blíže podívejme na metodu Update. Tato metoda sloučí všechny pohyby nebo rotace, které hráč vykonal pomocí jakéhokoli podporovaného vstupu, vygeneruje vektor rychlosti a aktualizuje naše hodnoty náklonu a otáčení, aby k nim měla herní smyčka přístup.

Metoda Update zahájí proces voláním UpdatePollingDevices, aby aktualizovala stav ovladače. Tato metoda také shromažďuje jakýkoli vstup z herního panelu a přidává jeho pohyby do m_moveCommand vektoru.

V naší metodě Update pak provedeme následující kontroly vstupu.

  • Pokud hráč používá obdélník ovladače pro přesunutí, určíme změnu pozice ukazatele a použijeme ho k výpočtu, jestli uživatel přesunul ukazatel mimo neaktivní zónu kontroleru. Pokud se nacházíte mimo mrtvou zónu, vlastnost m_moveCommand vektoru se pak aktualizuje hodnotou virtuálního joysticku.
  • Pokud je stisknut některý ze vstupů klávesnice pro pohyb, je hodnota 1.0f nebo -1.0f je přidána do odpovídající součásti m_moveCommand vektoru –1.0f pro dopředu a -1.0f dozadu.

Jakmile se zohlední veškerý vstup pohybu, spustíme vektor m_moveCommand pomocí některých výpočtů, abychom vygenerovali nový vektor, který představuje směr hráče s ohledem na herní svět. Pak vezmeme své pohyby ve vztahu ke světu a aplikujeme je na hráče jako rychlost v daném směru. Nakonec resetujeme vektor m_moveCommand na (0.0f, 0.0f, 0.0f), aby vše bylo připravené na další herní rámec.

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

Další kroky

Teď, když jsme přidali naše ovládací prvky, je tu další funkce, kterou potřebujeme přidat k vytvoření imerzivní hry: zvuk! Hudba a zvukové efekty jsou důležité pro každou hru, takže pojďme se nyní podívat na přidání zvuku.