Giriş
Bırakın acemiler klavyelerini macera oyunları için, değerli çoklu dokunmatik parmak uçlarını meyve kesmek için ve süslü yeni hareket sensörlerini Michael Jackson gibi dans edebildiklerini iddia etmek için kullansınlar. (Söylememize gerek yok ama bunu yapamazlar.) Ama sen farklısın. Daha iyisiniz. Tam bir profesyonelsiniz. Oyunlar sizin için oyun kumandasıyla başlar ve biter.
Ama bir saniye. Web uygulamanızda oyun kumandası desteği sunmak istiyorsanız şansınız yok mu? Artık sorun değil. Yepyeni Gamepad API, bilgisayarınıza bağlı herhangi bir oyun kumandasının durumunu okumak için JavaScript kullanmanıza olanak tanıyarak imdadınıza yetişir. Bu özellik o kadar yeni ki Chrome 21'e daha geçen hafta eklendi ve Firefox'ta da desteklenmek üzere (şu anda özel bir derlemede mevcut).
Bu, oldukça iyi bir zamanlama oldu çünkü kısa süre önce Engelli Koşu 2012 Google doodle'ında bu özelliği kullanma fırsatı bulduk. Bu makalede, Gamepad API'yi doodle'a nasıl eklediğimiz ve bu süreçte neler öğrendiğimiz kısaca açıklanmaktadır.
Oyun kumandası test aracı
Etkileşimli doodle'lar geçici olmalarına rağmen arka planda oldukça karmaşık bir yapıya sahiptir. Anlattıklarımızı daha kolay gösterebilmek için Doodle'daki oyun kumandası kodunu alıp basit bir oyun kumandası test aracı oluşturduk. USB oyun kumandanızın düzgün çalışıp çalışmadığını görmek ve nasıl yapıldığını incelemek için bu uygulamayı kullanabilirsiniz.
Şu anda hangi tarayıcılar bu özelliği destekliyor?
Hangi oyun kumandaları kullanılabilir?
Genellikle, sisteminiz tarafından yerel olarak desteklenen tüm modern oyun kumandaları çalışır. PC'de markasız USB kumandalar, Mac'te dongle ile bağlanan PlayStation 2 oyun kumandaları ve Chrome OS dizüstü bilgisayarla eşlenen Bluetooth kumandalar gibi çeşitli oyun kumandalarını test ettik.
Bu, doodle'ımızı test etmek için kullandığımız bazı kumandaların fotoğrafı. "Evet anne, işte gerçekten bunu yapıyorum." Kumandanız çalışmıyorsa veya kontroller yanlış eşlenmişse lütfen Chrome veya Firefox için hata bildiriminde bulunun. (Sorunun zaten düzeltilmediğinden emin olmak için lütfen her tarayıcının en yeni sürümünde test edin.)
Gamepad API'yi algılama özelliği<
Chrome'da:
var gamepadSupportAvailable = !!navigator.webkitGetGamepads || !!navigator.webkitGamepads;
Bu durumun Firefox'ta algılanması henüz mümkün görünmüyor. Her şey etkinliğe dayalıdır ve tüm etkinlik işleyicilerin pencereye eklenmesi gerekir. Bu da etkinlik işleyicileri algılamaya yönelik tipik bir tekniğin çalışmasını engeller.
Ancak bunun geçici bir durum olduğundan eminiz. Muhteşem Modernizr, Gamepad API hakkında bilgi verir. Bu nedenle, mevcut ve gelecekteki tüm algılama ihtiyaçlarınız için bunu öneririz:
var gamepadSupportAvailable = Modernizr.gamepads;
Bağlı oyun kumandaları hakkında bilgi edinme
Oyun kumandasını bağlasanız bile kullanıcı düğmelerinden birine basmadığı sürece oyun kumandası hiçbir şekilde görünmez. Bu, dijital parmak izi oluşturmayı önlemek için yapılır ancak kullanıcı deneyimi açısından biraz zorluk yaratır: Kullanıcıdan düğmeye basmasını isteyemez veya oyun kumandasına özel talimatlar veremezsiniz çünkü kumandayı bağlayıp bağlamadığını bilemezsiniz.
Ancak bu engeli aştıktan sonra (üzgünüz…) daha fazlası sizi bekliyor.
Anket
Chrome'un API uygulaması, sisteme takılı tüm oyun kumandalarının listesini ve mevcut durumlarını (düğmeler + çubuklar) almak için kullanabileceğiniz bir işlev (navigator.webkitGetGamepads()) sunar. Bağlı ilk oyun kumandası, dizideki ilk giriş olarak döndürülür ve bu şekilde devam eder.
(Bu işlev çağrısı, doğrudan erişebileceğiniz bir dizinin yerini aldı: navigator.webkitGamepads[]. Ağustos 2012'nin başından itibaren, Chrome 21'de bu diziye erişmek hâlâ gereklidir. İşlev çağrısı ise Chrome 22 ve sonraki sürümlerde çalışır. Bundan sonra, API'yi kullanmak için işlev çağrısı önerilir ve bu yöntem, yüklü tüm Chrome tarayıcılara kademeli olarak sunulur.)
Spesifikasyonun şu ana kadar uygulanan kısmı, değişiklikler olduğunda etkinlikleri tetiklemek yerine bağlı oyun kumandalarının durumunu sürekli olarak kontrol etmenizi (ve gerekirse öncekiyle karşılaştırmanızı) gerektirir. Sorgulamayı en verimli ve pil dostu şekilde ayarlamak için requestAnimationFrame() yöntemini kullandık. Doodle'ımızda animasyonları destekleyen bir requestAnimationFrame() döngüsü olmasına rağmen tamamen ayrı ikinci bir döngü oluşturduk. Bu döngünün kodlanması daha kolaydı ve performansı hiçbir şekilde etkilemeyecekti.
Test kullanıcısının kodu:
/**
* Starts a polling loop to check for gamepad state.
*/
startPolling: function() {
// Don't accidentally start a second loop, man.
if (!gamepadSupport.ticking) {
gamepadSupport.ticking = true;
gamepadSupport.tick();
}
},
/**
* Stops a polling loop by setting a flag which will prevent the next
* requestAnimationFrame() from being scheduled.
*/
stopPolling: function() {
gamepadSupport.ticking = false;
},
/**
* A function called with each requestAnimationFrame(). Polls the gamepad
* status and schedules another poll.
*/
tick: function() {
gamepadSupport.pollStatus();
gamepadSupport.scheduleNextTick();
},
scheduleNextTick: function() {
// Only schedule the next frame if we haven't decided to stop via
// stopPolling() before.
if (gamepadSupport.ticking) {
if (window.requestAnimationFrame) {
window.requestAnimationFrame(gamepadSupport.tick);
} else if (window.mozRequestAnimationFrame) {
window.mozRequestAnimationFrame(gamepadSupport.tick);
} else if (window.webkitRequestAnimationFrame) {
window.webkitRequestAnimationFrame(gamepadSupport.tick);
}
// Note lack of setTimeout since all the browsers that support
// Gamepad API are already supporting requestAnimationFrame().
}
},
/**
* Checks for the gamepad status. Monitors the necessary data and notices
* the differences from previous state (buttons for Chrome/Firefox,
* new connects/disconnects for Chrome). If differences are noticed, asks
* to update the display accordingly. Should run as close to 60 frames per
* second as possible.
*/
pollStatus: function() {
// (Code goes here.)
},
Yalnızca bir oyun kumandasıyla ilgileniyorsanız verilerini almak şu kadar basit olabilir:
var gamepad = navigator.webkitGetGamepads && navigator.webkitGetGamepads()[0];
Daha akıllıca bir şey yapmak veya aynı anda birden fazla oyuncuyu desteklemek istiyorsanız daha karmaşık senaryolara (iki veya daha fazla oyun kumandası bağlı, bazılarının bağlantısı yarıda kesiliyor vb.) tepki vermek için birkaç satır daha kod eklemeniz gerekir. Bu sorunu çözmek için kullanabileceğiniz bir yaklaşımı görmek isterseniz test aracımızın kaynak koduna (pollGamepads() işlevi) bakabilirsiniz.
Etkinlikler
Firefox, Gamepad API spesifikasyonunda açıklanan alternatif ve daha iyi bir yöntem kullanır. Sizden yoklama yapmanızı istemek yerine, bir oyun kumandası takıldığında (veya daha doğrusu, takılıp düğmelerinden herhangi birine basılarak "duyurulduğunda") ya da çıkarıldığında tetiklenen iki etkinlik (MozGamepadConnected ve MozGamepadDisconnected) sunar. Gelecekteki durumunu yansıtmaya devam edecek olan oyun kumandası nesnesi, etkinlik nesnesinin .gamepad parametresi olarak iletilir.
Test cihazı kaynak kodundan:
/**
* React to the gamepad being connected. Today, this will only be executed
* on Firefox.
*/
onGamepadConnect: function(event) {
// Add the new gamepad on the list of gamepads to look after.
gamepadSupport.gamepads.push(event.gamepad);
// Start the polling loop to monitor button changes.
gamepadSupport.startPolling();
// Ask the tester to update the screen to show more gamepads.
tester.updateGamepads(gamepadSupport.gamepads);
},
Özet
Sonuç olarak, test aracındaki başlatma işlevimiz her iki yaklaşımı da destekler ve şu şekilde görünür:
/**
* Initialize support for Gamepad API.
*/
init: function() {
// As of writing, it seems impossible to detect Gamepad API support
// in Firefox, hence we need to hardcode it in the third clause.
// (The preceding two clauses are for Chrome.)
var gamepadSupportAvailable = !!navigator.webkitGetGamepads ||
!!navigator.webkitGamepads ||
(navigator.userAgent.indexOf('Firefox/') != -1);
if (!gamepadSupportAvailable) {
// It doesn't seem Gamepad API is available – show a message telling
// the visitor about it.
tester.showNotSupported();
} else {
// Firefox supports the connect/disconnect event, so we attach event
// handlers to those.
window.addEventListener('MozGamepadConnected',
gamepadSupport.onGamepadConnect, false);
window.addEventListener('MozGamepadDisconnected',
gamepadSupport.onGamepadDisconnect, false);
// Since Chrome only supports polling, we initiate polling loop straight
// away. For Firefox, we will only do it if we get a connect event.
if (!!navigator.webkitGamepads || !!navigator.webkitGetGamepads) {
gamepadSupport.startPolling();
}
}
},
Oyun kumandası bilgileri
Sisteme bağlı her oyun kumandası, şuna benzer bir nesneyle temsil edilir:
id: "PLAYSTATION(R)3 Controller (STANDARD GAMEPAD Vendor: 054c Product: 0268)"
index: 1
timestamp: 18395424738498
buttons: Array[8]
0: 0
1: 0
2: 1
3: 0
4: 0
5: 0
6: 0.03291
7: 0
axes: Array[4]
0: -0.01176
1: 0.01961
2: -0.00392
3: -0.01176
Temel bilgiler
En üstteki birkaç alan basit meta verilerdir:
id: Oyun kumandasının metin açıklamasıindex: Bir bilgisayara bağlı farklı oyun kumandalarını ayırt etmek için kullanılan bir tam sayıtimestamp: düğme/eksen durumunun son güncellemesinin zaman damgası (şu anda yalnızca Chrome'da desteklenmektedir)
Düğmeler ve çubuklar
Günümüzün oyun kumandaları, büyükbabanızın prensesi yanlış kaleden kurtarmak için kullandığı kumandalara pek benzemiyor. Genellikle iki analog çubuğun yanı sıra en az on altı ayrı düğme (bazıları ayrı, bazıları analog) bulunuyor. Gamepad API, işletim sistemi tarafından bildirilen tüm düğmeler ve analog çubuklar hakkında bilgi verir.
Gamepad nesnesinde mevcut durumu aldıktan sonra düğmelere .buttons[], çubuklara ise .axes[] dizileri aracılığıyla erişebilirsiniz. Bunların neye karşılık geldiğini gösteren görsel bir özeti aşağıda bulabilirsiniz:
Spesifikasyon, tarayıcıdan ilk on altı düğmeyi ve dört ekseni şunlarla eşleştirmesini ister:
gamepad.BUTTONS = {
FACE_1: 0, // Face (main) buttons
FACE_2: 1,
FACE_3: 2,
FACE_4: 3,
LEFT_SHOULDER: 4, // Top shoulder buttons
RIGHT_SHOULDER: 5,
LEFT_SHOULDER_BOTTOM: 6, // Bottom shoulder buttons
RIGHT_SHOULDER_BOTTOM: 7,
SELECT: 8,
START: 9,
LEFT_ANALOGUE_STICK: 10, // Analogue sticks (if depressible)
RIGHT_ANALOGUE_STICK: 11,
PAD_TOP: 12, // Directional (discrete) pad
PAD_BOTTOM: 13,
PAD_LEFT: 14,
PAD_RIGHT: 15
};
gamepad.AXES = {
LEFT_ANALOGUE_HOR: 0,
LEFT_ANALOGUE_VERT: 1,
RIGHT_ANALOGUE_HOR: 2,
RIGHT_ANALOGUE_VERT: 3
};
Ek düğmeler ve eksenler yukarıdakilere eklenir. Ancak ne on altı düğmenin ne de dört eksenin garanti edilmediğini unutmayın. Bazılarının tanımlanmamış olmasına hazırlıklı olun.
Düğmeler 0,0 (basılmamış) ile 1,0 (tamamen basılmış) arasında değerler alabilir. Eksenler -1,0 (tamamen sol veya yukarı) ile 0,0 (orta) ve 1,0 (tamamen sağ veya aşağı) arasında değişir.
Analog mu yoksa ayrık mı?
Görünüşe göre her düğme analog olabilir. Örneğin, omuz düğmeleri için bu durum yaygındır. Bu nedenle, değeri doğrudan 1,00 ile karşılaştırmak yerine bir eşik belirlemek daha iyidir (örneğin, analog düğme biraz kirliyse ne olur? Hiçbir zaman 1,00'e ulaşmayabilir.) Doodle'ımızda bunu şu şekilde yapıyoruz:
gamepad.ANALOGUE_BUTTON_THRESHOLD = .5;
gamepad.buttonPressed_ = function(pad, buttonId) {
return pad.buttons[buttonId] &&
(pad.buttons[buttonId] > gamepad.ANALOGUE_BUTTON_THRESHOLD);
};
Analog çubukları dijital joystick'lere dönüştürmek için de aynı işlemi yapabilirsiniz. Elbette her zaman dijital ped (d-pad) vardır ancak oyun kumandanızda d-pad olmayabilir. Bunu işlemek için kullanacağımız kod şu şekildedir:
gamepad.AXIS_THRESHOLD = .75;
gamepad.stickMoved_ = function(pad, axisId, negativeDirection) {
if (typeof pad.axes[axisId] == 'undefined') {
return false;
} else if (negativeDirection) {
return pad.axes[axisId] < -gamepad.AXIS_THRESHOLD;
} else {
return pad.axes[axisId] > gamepad.AXIS_THRESHOLD;
}
};
Düğmeye basma ve çubuk hareketleri
Etkinlikler
Uçuş simülatörü oyunu gibi bazı durumlarda, kumanda kolu konumlarını veya düğme basışlarını sürekli kontrol edip bunlara tepki vermek daha mantıklıdır. Ancak Hurdles 2012 doodle'ı gibi şeyler için bu geçerli değildir. "Neden her karede düğme olup olmadığını kontrol etmem gerekiyor?" diye düşünebilirsiniz. Klavye veya fare yukarı/aşağı hareketlerinde olduğu gibi neden etkinlik alamıyorum?
İyi haber şu ki bunu yapabilirsiniz. Kötü haber ise gelecekte. Spesifikasyonda yer alsa da henüz hiçbir tarayıcıda uygulanmamıştır.
Anket
Bu arada, mevcut ve önceki durumu karşılaştırıp herhangi bir fark görürseniz işlevleri çağırabilirsiniz. Örneğin:
if (buttonPressed(pad, 0) != buttonPressed(oldPad, 0)) {
buttonEvent(0, buttonPressed(pad, 0) ? 'down' : 'up');
}
for (var i in gamepadSupport.gamepads) {
var gamepad = gamepadSupport.gamepads[i];
// Don't do anything if the current timestamp is the same as previous
// one, which means that the state of the gamepad hasn't changed.
// This is only supported by Chrome right now, so the first check
// makes sure we're not doing anything if the timestamps are empty
// or undefined.
if (gamepadSupport.prevTimestamps[i] &&
(gamepad.timestamp == gamepadSupport.prevTimestamps[i])) {
continue;
}
gamepadSupport.prevTimestamps[i] = gamepad.timestamp;
gamepadSupport.updateDisplay(i);
}
2012 Engelli Koşu doodle'ında klavye öncelikli yaklaşım
Oyun kumandası olmadan bugünkü doodle'ımızın tercih edilen giriş yöntemi klavye olduğundan, oyun kumandasının klavyeyi oldukça yakından taklit etmesine karar verdik. Bu, üç karar anlamına geliyordu:
Doodle'da yalnızca üç düğme (koşmak için iki, zıplamak için bir) gerekir ancak oyun kumandasında muhtemelen daha fazla düğme vardır. Bu nedenle, bilinen on altı düğmenin ve iki çubuğun tümünü, en mantıklı olduğunu düşündüğümüz şekilde bu üç mantıksal işleve eşledik. Böylece kullanıcılar şu yöntemlerle koşabilir: A/B düğmelerini sırayla kullanma, omuz düğmelerini sırayla kullanma, d-pad'de sola/sağa basma veya çubuklardan birini sertçe sola ve sağa sallama (Bunlardan bazıları elbette diğerlerinden daha verimli olacaktır.). Örneğin:
newState[gamepad.STATES.LEFT] = gamepad.buttonPressed_(pad, gamepad.BUTTONS.PAD_LEFT) || gamepad.stickMoved_(pad, gamepad.AXES.LEFT_ANALOGUE_HOR, true) || gamepad.stickMoved_(pad, gamepad.AXES.RIGHT_ANALOGUE_HOR, true), newState[gamepad.STATES.PRIMARY_BUTTON] = gamepad.buttonPressed_(pad, gamepad.BUTTONS.FACE_1) || gamepad.buttonPressed_(pad, gamepad.BUTTONS.LEFT_SHOULDER) || gamepad.buttonPressed_(pad, gamepad.BUTTONS.LEFT_SHOULDER_BOTTOM) || gamepad.buttonPressed_(pad, gamepad.BUTTONS.SELECT) || gamepad.buttonPressed_(pad, gamepad.BUTTONS.START) || gamepad.buttonPressed_(pad, gamepad.BUTTONS.LEFT_ANALOGUE_STICK),Daha önce açıklanan eşik işlevlerini kullanarak her analog girişi ayrı bir giriş olarak ele aldık.
Gamepad girişini doodle'a yerleştirmek yerine, doodle'a bağladık. Yoklama döngümüz, gerekli keydown ve keyup etkinliklerini (uygun bir keyCode ile) sentezleyip DOM'a geri gönderir:
// Create and dispatch a corresponding key event.
var event = document.createEvent('Event');
var eventName = down ? 'keydown' : 'keyup';
event.initEvent(eventName, true, true);
event.keyCode = gamepad.stateToKeyCodeMap_[state];
gamepad.containerElement_.dispatchEvent(event);
Hepsi bu kadar.
İpuçları ve püf noktaları
- Bir düğmeye basılmadan önce oyun kumandasının tarayıcınızda görünmeyeceğini unutmayın.
- Oyun kumandasını aynı anda farklı tarayıcılarda test ediyorsanız kumandayı yalnızca bir tarayıcının algılayabileceğini unutmayın. Herhangi bir etkinlik almıyorsanız bu özelliği kullanıyor olabilecek diğer sayfaları kapattığınızdan emin olun. Ayrıca, deneyimlerimize göre bazen sekmeyi kapatsanız veya tarayıcıdan çıksanız bile tarayıcı, oyun kumandasını "tutabilir". Bazen sorunları düzeltmenin tek yolu sistemi yeniden başlatmaktır.
- Her zaman olduğu gibi, en iyi desteği aldığınızdan emin olmak için Chrome Canary'yi ve diğer tarayıcıların eşdeğerlerini kullanın. Ardından, eski sürümlerin farklı davrandığını görürseniz uygun şekilde hareket edin.
Gelecek
Bu yazının, henüz biraz istikrarsız olsa da şimdiden çok eğlenceli olan bu yeni API'ye biraz ışık tutacağını umuyoruz.
API'nin eksik parçalarına (ör. etkinlikler) ve daha geniş tarayıcı desteğine ek olarak, zaman içinde sarsıntı kontrolü, yerleşik jiroskoplara erişim vb. özelliklerin de eklenmesini umuyoruz. Ayrıca, farklı türde oyun kumandaları için daha fazla destek sunulmasını bekliyoruz. Yanlış çalışan veya hiç çalışmayan bir oyun kumandası bulursanız lütfen Chrome için hata bildiriminde bulunun ve/veya Firefox için hata bildiriminde bulunun.
Ancak bundan önce 2012 Engelli Koşu Doodle'ımızla oynayın ve oyun kumandasıyla oynamanın ne kadar eğlenceli olduğunu görün. 10, 7 saniyeden daha iyi bir süre yapabileceğinizi mi söylediniz? Getir.