Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Uyarı
Bu konu, DirectX öğretici serisi ile basit bir Evrensel Windows Platformu (UWP) oluşturma
[ Windows 10'da UWP uygulamaları için güncelleştirildi. Windows 8.x makaleleri için bkz. arşiv ]
İyi bir Evrensel Windows Platformu (UWP) oyunu çok çeşitli arabirimleri destekler. Potansiyel bir oyuncunun fiziksel düğmesi olmayan bir tablette Windows 10, oyun kumandası takılı bir bilgisayar veya yüksek performanslı fare ve oyun klavyesi ile en son masaüstü oyun makinesi olabilir. Oyunumuzda denetimler MoveLookController sınıfında uygulanır. Bu sınıf, üç giriş türünün tümünü (fare ve klavye, dokunma ve oyun yüzeyi) tek bir denetleyicide toplar. Sonuç, birden çok cihazla uyumlu, türe uygun standart hareket ve bakış kontrolleri kullanan birinci şahıs nişancı oyunudur.
Uyarı
Denetimler hakkında daha fazla bilgi için bkz. Oyunlar için hareket-görüş denetimleri ve oyunlar için dokunma denetimleri.
Amaç
Bu noktada işleyen bir oyunumuz var, ancak oyuncumuzu hareket ettiremiyoruz veya hedefleri vuramıyoruz. Oyunumuzun UWP DirectX oyunumuzda aşağıdaki giriş türleri için birinci şahıs nişancı hareket görünümü denetimlerini nasıl uyguladığına göz atacağız.
- Fare ve klavye
- Dokunmak
- Oyun kumandası
Uyarı
Bu örnek için en son oyun kodunu indirmediyseniz Direct3D örnek oyunbölümüne gidin. Bu örnek, büyük bir UWP özellik örnekleri koleksiyonunun bir parçasıdır. Örneği indirme talimatları için, Windows geliştirme için örnek uygulamalar sayfasına bakın.
Yaygın denetim davranışları
Dokunmatik denetimler ve fare/klavye denetimleri çok benzer bir çekirdek uygulamaya sahiptir. UWP uygulamasında işaretçi yalnızca ekrandaki bir noktadır. Fareyi kaydırarak veya parmağınızı dokunmatik ekranda kaydırarak hareket ettirebilirsiniz. Sonuç olarak, tek bir olay kümesine kaydolabilir ve oyuncunun işaretçiyi hareket ettirmek ve basmak için fare mi yoksa dokunmatik ekran mı kullandığı konusunda endişelenmeyebilirsiniz.
Örnek oyundaki MoveLookController sınıfı başlatıldığında, işaretçiye özgü dört olay ve bir fareye özgü olay için kaydeder:
| Etkinlik | Açıklama |
|---|---|
| CoreWindow::P ointerPressed | Sol veya sağ fare düğmesine basıldı (ve tutuldu) veya dokunmatik yüzeye dokunuldu. |
| CoreWindow::P ointerMoved | Fare hareket etti veya dokunmatik yüzey üzerinde bir sürükleme eylemi yapıldı. |
| CoreWindow::P ointerReleased | Sol fare düğmesi serbest bırakıldı veya dokunmatik yüzeye temas eden nesne kaldırıldı. |
| CoreWindow::P ointerExited | İşaretçi ana pencerenin dışına taşındı. |
| Windows::D evices::Input::MouseMoved | Fare belirli bir mesafeyi hareket ettirdi. Geçerli X-Y konumuyla değil, yalnızca fare hareketi delta değerleriyle ilgilendiğimizi unutmayın. |
Bu olay işleyicileri, MoveLookController uygulama penceresinde başlatıldığı anda kullanıcı girişini dinlemeye başlayacak şekilde ayarlanır.
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 });
...
}
InitWindow için tam kod GitHub'da görülebilir.
Oyunun belirli girişleri ne zaman dinlemesi gerektiğini belirlemek için, MoveLookController sınıfı, denetleyici türüne bakılmaksızın denetleyiciye özgü üç duruma sahiptir:
| Devlet | Açıklama |
|---|---|
| Hiçbiri | Bu, denetleyici için başlatılan durumdur. Oyun herhangi bir denetleyici girişi tahmin etmediğinden tüm girişler yoksayılır. |
| GirdiBekle | Oyun kumandası, oyuncunun sol fare tıklaması, dokunma olayı ve oyun çubuğundaki menü düğmesini kullanarak oyundan gelen bir iletiyi onaylamasını bekliyor. |
| Etkin | Oyun kumandası etkin oyun modundadır. |
WaitForInput durumu ve oyunu duraklatma
Oyun duraklatıldığında WaitForInput durumuna girer. Bu durum, oyuncu işaretçiyi oyunun ana penceresinin dışına hareket ettiğinde veya duraklat düğmesine (P tuşu veya oyun çubuğu Başlangıç düğmesi) bastığında gerçekleşir. MoveLookController tuş basışını kaydeder ve IsPauseRequested yöntemini çağırdığında oyun döngüsünü bilgilendirir. Bu noktada IsPauseRequestedtruedöndürürse, oyun döngüsü daha sonra denetleyicideki MoveLookController üzerinde WaitForPress çağrısını yapar ve denetleyiciyi WaitForInput durumuna taşır.
WaitForInput durumuna geldikten sonra oyun, Etkin durumuna dönene kadar neredeyse tüm oyun giriş olaylarını işlemeyi durdurur. İstisna duraklatma düğmesi olup, bu düğmeye basıldığında oyun etkin duruma geri döner. Duraklat düğmesi dışında, oyunun Etkin durumuna geri dönmesi için oyuncunun bir menü öğesi seçmesi gerekir.
Etkin durum
Etkin durumu sırasında MoveLookController örneği, etkinleştirilen tüm giriş cihazlarından olayları işler ve oyuncunun niyetini yorumlar. Sonuç olarak, oyuncunun görüş açısının hızını ve yönünü güncelleştirir ve güncellenen verileri oyun döngüsünden Güncelleme çağrıldığında oyuna iletir.
Tüm işaretçi girişi, farklı işaretçi eylemlerine karşılık gelen farklı işaretçi kimlikleriyle Etkin durumunda izlenir. PointerPressed olayı alındığında, MoveLookController pencere tarafından oluşturulan işaretçi kimliği değerini alır. İşaretçi kimliği belirli bir giriş türünü temsil eder. Örneğin, çok dokunmalı bir cihazda aynı anda birkaç farklı etkin giriş olabilir. Kimlikler, oyuncunun hangi girişi kullandığını izlemek için kullanılır. Dokunmatik ekranın taşıma dikdörtgeninde bir olay varsa, taşıma dikdörtgenindeki işaretçi olaylarını izlemek için bir işaretçi kimliği atanır. Yangın dikdörtgenindeki diğer işaretçi olayları ayrı bir işaretçi kimliğiyle ayrı olarak izlenir.
Uyarı
Fare ve bir oyun kumandasının sağ başparmak çubuğundan gelen girişlerin de ayrı ayrı işlenen kimlikleri vardır.
İşaretçi olayları belirli bir oyun eylemiyle eşlendikten sonra, MoveLookController nesnenin ana oyun döngüsüyle paylaştığı
Çağrıldığında, örnek oyundaki Update yöntemi girişi işler ve hız ile bakış yönü değişkenlerini (m_velocity ve m_lookdirection) günceller. Oyun döngüsü, Velocity ve LookDirection yöntemlerini çağırarak alır.
Uyarı
Update yöntemi hakkında daha fazla bilgi bu sayfanın ilerleyen bölümlerinde görülebilir.
Oyun döngüsü, MoveLookController örneğinde IsFiring yöntemini çağırarak oyuncunun ateş edip etmediğini test edebilir. MoveLookController oyuncunun üç giriş türünden birinde ateş düğmesine basıp basmadığını denetler.
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;
}
Şimdi üç denetim türünün her birinin uygulanmasına biraz daha ayrıntılı bir şekilde göz atalım.
Göreli fare denetimleri ekleme
Fare hareketi algılandığında, kameranın yeni yükseklik ve yana yatış açılarını belirlemek için bu hareketi kullanmak istiyoruz. Bunu, hareketin mutlak x-y piksel koordinatlarını kaydetmek yerine farenin hareket ettiği göreli mesafeyi (hareketin başlangıcı ile durak arasındaki delta) işlediğimiz göreli fare denetimlerini uygulayarak yaparız.
Bunu yapmak için, MouseDelta::X ve MouseDelta::Y alanlarını Windows::Device::Input::MouseEventArgs::MouseDelta adlı bağımsız değişken nesnesi üzerinde inceleyerek X (yatay hareket) ve Y (dikey hareket) koordinatlarındaki değişiklikleri, MouseMoved olayı tarafından döndürülen, elde ederiz.
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;
}
}
Dokunma desteği ekleme
Dokunmatik denetimler, kullanıcıları tabletlerle desteklemek için mükemmeldir. Bu oyun, ekranın belirli alanlarını belirli oyun içi eylemlere göre hizalayarak dokunma girişi toplar. Bu oyunun dokunmatik girişi üç bölge kullanır.
Aşağıdaki komutlar dokunma denetimi davranışımızı özetler. Kullanıcı girişi | Eylem :------- | :-------- Dikdörtgeni taşı | Dokunmatik giriş, dikey hareketin ileri/geri konum hareketine, yatay hareketin ise sol/sağ konum hareketine dönüştürüleceği sanal bir joystick'e dönüştürülür. Yangın dikdörtgeni | Küreyi ateşle. Taşıma ve ateş etme dikdörtgeninin dışına dokunun | Kamera görünümünün döndürmesini (eğim ve yaw) değiştirin.
MoveLookController, olayın nerede oluştuğuna karar vermek için işaretçi kimliğini denetler ve aşağıdaki eylemlerden birini gerçekleştirir:
- Hareket veya ateşleme dikdörtgeninde PointerMoved olayı oluştuysa, denetleyici için işaretçi konumunu güncelleyin.
- PointerMoved olayı ekranın geri kalanında bir yerde (görünüm denetimleri olarak tanımlanır) oluştuysa, görünüm yön vektörünün eğim ve esneme değişimini hesaplayın.
Dokunma denetimlerimizi uyguladıktan sonra Direct2D kullanarak daha önce çizdiğimiz dikdörtgenler oyunculara hareket, ateş ve görünüm bölgelerinin nerede olduğunu gösterir.
dokunmatik denetimleri
Şimdi her denetimi nasıl uyguladığımıza göz atalım.
Hareket ve ateş etme kumandası
Ekranın sol alt çeyreğindeki hareket denetleyicisi dikdörtgeni yön paneli olarak kullanılır. Parmağınızı bu alanda sola ve sağa kaydırmak oyuncuyu sola ve sağa, yukarı ve aşağı kamerayı ileri ve geri hareket eder. Bunu ayarladıktan sonra, ekranın sağ alt çeyreğindeki ateş kontrolüne dokunulduğunda bir küre fırlatır.
SetMoveRect ve SetFireRect yöntemleri, giriş dikdörtgenlerimizi oluşturur ve her dikdörtgenin ekranın sol üst ve sağ alt köşe konumlarını belirtmek için iki, 2B vektör alır.
Ardından parametreler m_fireUpperLeft ve m_fireLowerRight atanır ve bu da kullanıcının dikdörtgenlerin içine dokunup dokunmadığını belirlememize yardımcı olur.
m_fireUpperLeft = upperLeft;
m_fireLowerRight = lowerRight;
Ekran yeniden boyutlandırılırsa, bu dikdörtgenler uygun boyuta yeniden çizilir.
Artık denetimlerimizin bölgelerini ayırdığımıza göre, bir kullanıcının bunları gerçekten ne zaman kullandığını belirlemenin zamanı geldi. Bunu yapmak için, MoveLookController::InitWindow yönteminde, kullanıcının işaretçisine basıp taşıması veya serbest bırakması için bazı olay işleyicileri ayarlarız.
window.PointerPressed({ this, &MoveLookController::OnPointerPressed });
window.PointerMoved({ this, &MoveLookController::OnPointerMoved });
window.PointerReleased({ this, &MoveLookController::OnPointerReleased });
İlk olarak, kullanıcı OnPointerPressed yöntemini kullanarak taşıma veya ateşleme dikdörtgenleri içine ilk kez bastığında ne olacağını belirleyeceğiz. Burada bir kontrole nereden dokunduklarını ve bir işaretçinin zaten bu kontrolün içinde olup olmadığını kontrol ediyoruz. Bu, belirli bir denetime dokunan ilk parmaksa, aşağıdakileri yaparız.
- Touchdown konumunu m_moveFirstDown veya m_fireFirstDown 2B vektör olarak depolayın.
- İşaretçi kimliğini m_movePointerID veya m_firePointerIDatayın.
- Artık bu denetim için etkin bir işaretçimiz olduğundan InUse bayrağını (
m_moveInUse veyam_fireInUse ) doğru şekildeolarak ayarlayın.
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;
...
}
}
...
Kullanıcının bir hareket veya ateş kontrolüne dokunup dokunmadığını belirlediğimize göre, oyuncunun basılı parmağıyla herhangi bir hareket yapıp yapmadığını görüyoruz. MoveLookController::OnPointerMoved yöntemini kullanarak, hangi işaretçinin taşındığını denetler ve yeni konumunu 2B vektör olarak depolarız.
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;
}
...
Kullanıcı denetimler içinde hareketlerini yaptıktan sonra işaretçiyi serbest bırakır. MoveLookController::OnPointerReleased yöntemini kullanarak, hangi işaretçinin serbest bırakıldığını belirler ve bir dizi sıfırlama yaparız.
Taşıma denetimi serbest bırakıldıysa aşağıdakileri gerçekleştiririz.
- Oyuncunun hızını, oyunda hareket etmelerini önlemek için tüm yönlerde
0olarak ayarlayın. - Kullanıcı artık hareket denetleyicisine dokunmadığından m_moveInUse'i
falseolarak değiştirin. - Taşıma denetleyicisinde artık işaretçi olmadığından taşıma işaretçisi kimliğini
0olarak ayarlayın.
if (pointerID == m_movePointerID)
{
// Stop on release.
m_velocity = XMFLOAT3(0, 0, 0);
m_moveInUse = false;
m_movePointerID = 0;
}
Yangın denetimi için, serbest bırakıldıysa, tek yapmamız gereken m_fireInUse bayrağını false ve yangın işaretçisi kimliğini 0 olarak değiştirmektir çünkü artık yangın denetiminde bir işaretçi yoktur.
else if (pointerID == m_firePointerID)
{
m_fireInUse = false;
m_firePointerID = 0;
}
Görünüm denetleyicisi
Ekranın kullanılmayan bölgeleri için dokunmatik cihaz işaretçisi olaylarını görünüm denetleyicisi olarak ele alıyoruz. Parmağınızı bu bölgede kaydırmak, oyuncu kamerasının eğimini ve esneğini (döndürme) değiştirir.
// 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;
}
Burada MoveLookController, olayı tetikleyen işaretçinin işaretçi kimliğini görünüm bölgesine karşılık gelen belirli bir değişkene atar. Görünüm bölgesinde bir dokunma olması durumunda, m_lookPointerID değişkeni olayı tetikleyen işaretçi kimliğine ayarlanır. m_lookInUseboole değişkeni, denetimin henüz serbest bırakılmadığını belirtmek için de ayarlanır.
Şimdi örnek oyunun PointerMoved dokunmatik ekran olayını nasıl işlediğine bakalım.
MoveLookController::OnPointerMoved yönteminde, olaya ne tür bir işaretçi kimliği atandığını denetleriz. m_lookPointerIDise, işaretçinin konumundaki değişikliği hesaplarız. Daha sonra döndürmenin ne kadar değişmesi gerektiğini hesaplamak için bu deltayı kullanırız. Son olarak, oyuncu rotasyonunu değiştirmek için oyunda kullanılacak m_pitch ve m_yaw güncelleştirebileceğimiz bir noktadayız.
// 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);
...
}
Bakacağımız son parça, örnek oyunun PointerReleased dokunmatik ekran olayını nasıl işlediğidir.
Kullanıcı dokunma hareketini tamamladıktan ve parmağı ekrandan kaldırdıktan sonra MoveLookController::OnPointerReleased başlatılır.
PointerReleased olayını tetikleyen işaretçinin kimliği, daha önce kaydedilmiş taşıma işaretçisinin kimliğiyse, MoveLookController, oyuncu görünüm alanına dokunmayı durdurduğundan, hızı 0'ya ayarlar.
else if (pointerID == m_lookPointerID)
{
m_lookInUse = false;
m_lookPointerID = 0;
}
Fare ve klavye desteği ekleme
Bu oyun klavye ve fare için aşağıdaki denetim düzenine sahiptir.
| Kullanıcı girişi | Eylem |
|---|---|
| W | Oyuncuyu ileri hareket ettir |
| A | Oynatıcıyı sola taşı |
| S | Oyuncuyu geriye hareket ettir |
| D | Oynatıcıyı sağa taşıma |
| X | Görünümü yukarı taşı |
| Boşluk tuşu | Görünümü aşağı taşı |
| P | Oyunu duraklatma |
| Fare hareketi | Kamera görünümünün dönme yönünü (eğim ve yan açısını) değiştirin. |
| Sol fare düğmesi | Küreyi ateşle |
Klavyeyi kullanmak için örnek oyun, CoreWindow::KeyUp ve CoreWindow::KeyDownolmak üzere iki yeni olayı MoveLookController::InitWindow yönteminde kaydeder. Bu olaylar bir tuşun basılması ve bırakılmasını işler.
window.KeyDown({ this, &MoveLookController::OnKeyDown });
window.KeyUp({ this, &MoveLookController::OnKeyUp });
Fare, işaretçi kullansa bile dokunma denetimlerinden biraz farklı şekilde ele alınıyor. Denetim düzenimizle hizalamak için MoveLookController
Bu, MoveLookControllerOnPointerPressed yönteminde işlenir.
Bu yöntemde, Windows::Devices::Input::PointerDeviceType sabit listesi ile hangi tür işaretçi cihazının kullanıldığını denetleriz.
Oyun
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;
Oyuncu fare düğmelerinden birine basmayı bıraktığında, CoreWindow::PointerReleased fare olayı tetiklenir, MoveLookController::OnPointerReleased yöntemi çağrılır ve giriş tamamlanır. Bu noktada, sol fare düğmesine basıldığında ve artık serbest bırakılırsa küreler ateş etmeyi durdurur. Görünüm her zaman etkin olduğundan, oyun devam eden görünüm olaylarını izlemek için aynı fare işaretçisini kullanmaya devam eder.
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;
Şimdi desteklediğimiz son denetim türüne bakalım: gamepad'ler. Oyun kumandaları, işaretçi nesnesini kullanmadıkları için dokunmatik ve fare denetimlerinden ayrı olarak işlenir. Bu nedenle, birkaç yeni olay işleyicisinin ve yönteminin eklenmesi gerekir.
Oyun kolu desteği ekleme
Bu oyun için, Windows.Gaming.Input API'lerine yapılan çağrılar ile gamepad desteği eklenir. Bu API kümesi yarış tekerlekleri ve uçuş çubukları gibi oyun kumandası girişlerine erişim sağlar.
Aşağıda gamepad kontrollerimiz yer alacaktır.
| Kullanıcı girişi | Eylem |
|---|---|
| Sol analog çubuk | Oyuncuyu taşı |
| Sağ analog çubuk | Kamera görünümünün dönme yönünü (eğim ve yan açısını) değiştirin. |
| Sağ tetikleyici | Küreyi ateşle |
| Başlat/Menü düğmesi | Oyunu duraklatma veya sürdürme |
InitWindow yönteminde, bir oyun çubuğunun eklenip veya kaldırılıpkaldırılmadığını belirlemek için iki yeni olay ekliyoruz. Bu olaylar m_gamepadsChanged özelliğini güncelleştirir. Bu, UpdatePollingDevices yönteminde bilinen oyun tuş takımı listesinin değişip değişmediğini denetlemek için kullanılır.
// Detect gamepad connection and disconnection events.
Gamepad::GamepadAdded({ this, &MoveLookController::OnGamepadAdded });
Gamepad::GamepadRemoved({ this, &MoveLookController::OnGamepadRemoved });
Uyarı
UWP uygulamaları odakta değilken oyun denetleyicisinden giriş alamaz.
UpdatePollingDevices yöntemi
MoveLookController örneğinin UpdatePollingDevices yöntemi, bir oyun kumandasının bağlı olup olmadığını kontrol etmek için hemen denetim yapar. Eğer bir tane varsa, Gamepad.GetCurrentReadingile durumunu okumaya başlayacağız. Bu, GamepadReading yapısını döndürür ve hangi düğmelere tıklandığını veya hangi parmak çubuklarının taşındığını kontrol etmemize olanak sağlar.
Oyunun durumu WaitForInputise, oyunun devam edebilmesi için oyun kumandasının yalnızca Başlat/Menü düğmesini dinleriz.
Etkinise, kullanıcının girişini denetler ve hangi oyun içi eylemin gerçekleşmesi gerektiğini belirleriz. Örneğin, kullanıcı sol analog çubuğu belirli bir yöne taşıdıysa, bu, oyunu çubuğun taşındığı yönde oyuncuyu hareket ettirmemiz gerektiğini bildirir. Çubuğun belirli bir yönde hareketi, ölü bölgeyarıçapından daha büyük olarak kaydedilmelidir; aksi takdirde hiçbir şey olmaz. Bu ölü bölge yarıçapı, denetleyicinin çubuğun üzerinde bekletilirken oyuncunun parmağından küçük hareketler aldığı "kaymayı" önlemek için gereklidir. Kullanıcıya, ölü bölgeler olmadan denetimler çok hassas görünebilir.
Parmak çubuğu girişi hem x hem de y ekseni için -1 ile 1 arasındadır. Aşağıdaki sabit, parmak çubuğu ölü bölgesinin yarıçapını belirtir.
#define THUMBSTICK_DEADZONE 0.25f
Bu değişkeni kullanarak eyleme dönüştürülebilir parmak çubuğu girişini işlemeye başlayacağız. Hareket, her iki eksende de [-1, -.26] veya [.26, 1] değerinden bir değerle gerçekleşir.
UpdatePollingDevices yönteminin bu parçası, sol ve sağ parmak çubuklarını işler. Her çubuğun X ve Y değerleri, ölü bölgenin dışında olup olmadığını görmek için denetlenır. Bunlardan biri veya her ikisi de ise ilgili bileşeni güncelleştireceğiz. Örneğin, sol parmak çubuğu X ekseni boyunca sola taşınıyorsa, m_moveCommand vektörünün x bileşenine -1 ekleyeceğiz. Bu vektör, tüm cihazlardaki tüm hareketleri toplamak için kullanılacaktır ve daha sonra oyuncunun nereye hareket etmesi gerektiğini hesaplamak için kullanılacaktır.
// 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;
}
Sol çubuğun hareketi kontrol etme şekline benzer şekilde, sağ çubuk kameranın dönüşünü kontrol eder.
Sağ başparmak çubuğu davranışı, fare ve klavye denetimi kurulumumuzda fare hareketi davranışıyla hizalanır. Çubuk ölü bölgenin dışındaysa, geçerli işaretçi konumu ile kullanıcının artık aramaya çalıştığı konum arasındaki farkı hesaplarız. İşaretçi konumundaki bu değişiklik (pointerDelta), daha sonra Update yöntemimize uygulanan kamera döndürmesinin yükseliş ve yalpa (yaw) açılarını güncellemek için kullanılır. pointerDelta vektörü, fare ve dokunmatik girişlerimizin işaretçi konumundaki değişikliği izlemek için MoveLookController::OnPointerMoved yönteminde de kullanıldığından tanıdık görünebilir.
// 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);
Küreleri ateşleyebilme özelliği olmadan oyunun kontrolleri tamamlanamaz!
Bu UpdatePollingDevices yöntemi de doğru tetikleyiciye basılıp basılmadığını denetler. Eğer doğruysa, m_firePressed özelliğimiz doğruya çevrilir ve bu, oyuna kürelerin ateş etmeye başlaması gerektiğinin sinyalini verir.
if (reading.RightTrigger > TRIGGER_DEADZONE)
{
if (!m_autoFire && !m_gamepadTriggerInUse)
{
m_firePressed = true;
}
m_gamepadTriggerInUse = true;
}
else
{
m_gamepadTriggerInUse = false;
}
Update yöntemi
Olayları toparlamak için Güncelleme yöntemini daha derinlemesine inceleyelim. Bu yöntem, oyuncunun desteklenen herhangi bir girişle yaptığı tüm hareketleri veya döndürmeleri birleştirerek bir hız vektörü oluşturur ve oyun döngümüze erişim için atış ve esneme değerlerimizi güncelleştirir.
Update yöntemi, denetleyicinin durumunu güncelleştirmek için UpdatePollingDevices çağırarak işleri başlatır. Bu yöntem ayrıca bir oyun çubuğundan herhangi bir giriş toplar ve hareketlerini m_moveCommand vektöre ekler.
Update yöntemimizde aşağıdaki giriş denetimlerini gerçekleştiriyoruz.
- Eğer oyuncu hareket denetleyicisi dikdörtgenini kullanıyorsa, işaretçi konumundaki değişikliği belirler ve bu değişikliği kullanarak kullanıcının işaretçiyi denetleyicinin ölü bölgesinin dışına çıkarıp çıkarmadığını hesaplarız. Ölü bölgenin dışındaysa, m_moveCommand vektör özelliği sanal kontrol cihazı değeriyle güncellenir.
- Hareket klavyesi girişlerinden herhangi birine basılırsa,
1.0fvektörünün ilgili bileşenine-1.0fveya değeri eklenir; ileriye doğru1.0fve geriye doğru-1.0f.
Tüm hareket girişleri dikkate alındıktan sonra, oyuncu yönünü oyun dünyasına göre temsil eden yeni bir vektör oluşturmak için bazı hesaplamalar aracılığıyla m_moveCommand vektörünü çalıştırırız.
Ardından hareketlerimizi dünyaya göre alıp bu yöndeki hız olarak oyuncuya uyguluyoruz.
Son olarak, her şeyin bir sonraki oyun çerçevesi için hazır olması için m_moveCommand vektörlerini (0.0f, 0.0f, 0.0f) olarak sıfırladık.
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);
}
Sonraki Adımlar
Denetimlerimizi eklediğimize göre, sürükleyici bir oyun oluşturmak için eklememiz gereken başka bir özellik daha var: ses! Müzik ve ses efektleri her oyun için önemlidir, bu nedenle bir sonraki olarak ses ekleme'yi tartışalım.