Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
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.
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.
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_moveInUse
false, 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.
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.0fvektor megfelelő összetevőjében-1.0fvagy értéket ad hozzá–1.0felőre és-1.0fvisszafelé.
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.