Giriş
Geçen yılın sonunda iOS ve Android'de Bouncy Mouse'u yayınladıktan sonra birkaç önemli ders öğrendim. Bunların en önemlisi, yerleşik bir pazara girmenin zor olmasıydı. Tamamen doygun iPhone pazarında ilgi çekmek çok zordu. Daha az doygun Android Marketplace'te ilerleme kaydetmek daha kolaydı ancak yine de kolay değildi. Bu deneyimden yola çıkarak Chrome Web Mağazası'nda ilginç bir fırsat gördüm. Web Mağazası'nda hiç oyun yokmuş gibi görünmese de yüksek kaliteli HTML5 tabanlı oyun kataloğumuz yeni yeni gelişmeye başladı. Yeni bir uygulama geliştirici için bu, sıralama listelerine girmenin ve görünür olmanın çok daha kolay olduğu anlamına gelir. Bu fırsatı göz önünde bulundurarak, en son oyun deneyimimi heyecan verici yeni bir kullanıcı tabanına sunma umuduyla Bouncy Mouse'u HTML5'e taşımaya karar verdim. Bu örnek olayda, Bouncy Mouse'u HTML5'e taşımayla ilgili genel süreç hakkında biraz bilgi vereceğim. Ardından, ilgi çekici olduğunu düşündüğüm üç konuyu daha ayrıntılı olarak ele alacağım: Ses, Performans ve Para Kazanma.
C++ Oyununu HTML5'e Taşıma
Bouncy Mouse şu anda Android(C++), iOS (C++), Windows Phone 7 (C#) ve Chrome (Javascript)'da kullanılabilir. Bu durum bazen şu soruyu gündeme getirir: Birden fazla platforma kolayca taşınabilecek bir oyun nasıl yazılır? Kullanıcıların, manuel taşımaya başvurmadan bu düzeyde taşınabilirlik elde etmek için kullanabilecekleri sihirli bir çözüm beklediğini düşünüyorum. Maalesef henüz böyle bir çözümün mevcut olduğundan emin değilim (Buna en yakın şey muhtemelen Google'ın PlayN çerçevesi veya Unity motoru olsa da bunların hiçbiri ilgilendiğim tüm hedeflere ulaşmıyor). Yaklaşımım aslında manuel taşımaydı. Önce iOS/Android sürümünü C++'da yazdım, ardından bu kodu her yeni platforma taşıdım. Bu çok fazla iş gibi görünse de WP7 ve Chrome sürümlerinin her birinin tamamlanması en fazla 2 hafta sürdü. Şimdi soru şu: Kod tabanını kolayca taşınabilir hale getirmek için bir şey yapılabilir mi? Bu konuda yardımcı olan birkaç şey yaptım:
Kod tabanını küçük tutun
Bu, kulağa çok normal gelebilir ancak oyunu bu kadar hızlı taşıyabilmemin asıl nedeni bu. Bouncy Mouse'un istemci kodu yalnızca yaklaşık 7.000 satır C++'dan oluşur. 7.000 satır kod az değildir ancak yönetilebilir olacak kadar küçüktür. İstemci kodunun hem C# hem de JavaScript sürümleri yaklaşık olarak aynı boyutta oldu. Kod tabanım küçük tutmak için temel olarak iki önemli uygulamayı kullandım: Fazla kod yazmayın ve mümkün olduğunca çok işlemi ön işleme (çalışma zamanında olmayan) kodunda yapın. Fazla kod yazmamak akla yatkın bir şey gibi görünse de her zaman kendimle mücadele ettiğim bir konudur. Yardımcı sınıfa dahil edilebilecek her şey için sık sık yardımcı sınıf/işlevi yazmak istiyorum. Ancak, yardımcıyı birden çok kez kullanmayı planlamıyorsanız genellikle kodunuzu şişirir. Bouncy Mouse için, en az üç kez kullanmayacağım yardımcıları yazmamaya özen gösterdim. Yardımcı sınıf yazarken bunu gelecekteki projelerimde kullanabileceğim şekilde temiz, taşınabilir ve yeniden kullanılabilir hale getirmeye çalıştım. Öte yandan, yeniden kullanım olasılığı düşük olan ve yalnızca Bouncy Mouse için kod yazarken, kod yazmanın "en güzel" yolu olmasa bile kodlama görevini olabildiğince basit ve hızlı bir şekilde tamamlamaya odaklandım. Kod tabanını küçük tutmanın ikinci ve daha önemli kısmı, mümkün olduğunca fazla işlemi ön işleme adımlarına itmekti. Bir çalışma zamanı görevini alıp ön işleme görevine taşıyabilirseniz oyununuz daha hızlı çalışır ve kodu her yeni platforma taşımak zorunda kalmazsınız. Örneğin, başlangıçta seviye geometrisi verilerimi oldukça işlenmemiş bir biçimde depoladım ve gerçek OpenGL/WebGL köşe düğümü arabelleklerini çalışma zamanında birleştirdim. Bu işlem için biraz kurulum ve birkaç yüz satır çalışma zamanı kodu gerekiyordu. Daha sonra bu kodu bir ön işleme adımına taşıyarak derleme zamanında tamamen paketlenmiş OpenGL/WebGL köşe düğümü arabelleklerini yazdım. Gerçek kod miktarı yaklaşık olarak aynıydı ancak bu birkaç yüz satır bir ön işleme adımına taşınmıştı. Yani bu satırları yeni platformlara taşımak zorunda kalmadım. Bouncy Mouse'ta bununla ilgili çok sayıda örnek vardır. Yapabilecekleriniz oyundan oyuna değişir ancak çalışma zamanında gerçekleşmesi gerekmeyen her şeye dikkat edin.
İhtiyacınız olmayan bağımlılıklara sahip olmayın
Bouncy Mouse'un kolay taşınmasının bir diğer nedeni de neredeyse hiç bağımlılık içermemesidir. Aşağıdaki grafikte, Bouncy Mouse'un platforma göre başlıca kitaplık bağımlılıkları özetlenmiştir:
Hepsi bu kadar. Tüm platformlarda taşınabilir olan Box2D dışında büyük üçüncü taraf kitaplıkları kullanılmamıştır. Grafikler söz konusu olduğunda hem WebGL hem de XNA, OpenGL ile neredeyse 1:1 eşleştiğinden bu büyük bir sorun değildi. Gerçek kitaplıklar yalnızca ses alanında farklıydı. Ancak Bouncy Mouse'taki ses kodu küçük olduğundan (platforma özgü yaklaşık yüz satır kod) bu çok büyük bir sorun değildi. Bouncy Mouse'u taşınamayan büyük kitaplıklardan uzak tutmak, çalışma zamanı kodunun mantığının (dil değişikliğine rağmen) sürümler arasında neredeyse aynı olabileceği anlamına gelir. Ayrıca, taşınabilir olmayan bir araç zincirine bağlı kalmamızı önler. OpenGL/WebGL ile kodlamanın, Cocos2D veya Unity gibi bir kitaplık kullanmaya kıyasla doğrudan karmaşıklığı artırıp artırmadığı soruldu (WebGL yardımcıları da mevcuttur). Aslında tam tersine inanıyorum. Çoğu cep telefonu / HTML5 oyunu (en azından Bouncy Mouse gibi oyunlar) çok basittir. Çoğu durumda oyun yalnızca birkaç sprite ve belki de bazı dokulu geometriler çizer. Bouncy Mouse'ta OpenGL'e özgü kodun toplamı muhtemelen 1.000 satırdan azdır. Yardımcı kitaplık kullanmak bu sayıyı gerçekten azaltırsa şaşırırım. Bu sayıyı yarıya indirse bile, yalnızca 500 satır kod tasarrufu sağlamak için yeni kitaplıkları/araçları öğrenmek üzere önemli miktarda zaman harcamam gerekir. Ayrıca, ilgilendiğim tüm platformlarda taşınabilir bir yardımcı kitaplık bulamadım. Bu nedenle, böyle bir bağımlılık kullanmak taşınabilirliği önemli ölçüde olumsuz etkileyecektir. Işık haritaları, dinamik LOD, deri kaplı animasyon vb. gerektiren bir 3D oyun yazıyor olsaydım yanıtım kesinlikle değişirdi. Bu durumda, motorumun tamamını OpenGL için elle kodlamaya çalışmakla tekerleği yeniden icat ediyor olurdum. Buradaki amacım, çoğu mobil/HTML5 oyununun (henüz) bu kategoride olmadığından, gerekli olmadan işleri karmaşıklaştırmanın gerekmediğidir.
Diller arasındaki benzerlikleri küçümsemeyin
C++ kod tabanımımı yeni bir dile taşıma sürecinde çok zaman kazandıran son bir numara da kodun büyük kısmının her dil için neredeyse aynı olduğunu fark etmemdi. Bazı önemli öğeler değişebilir ancak bunlar, değişmeyen öğelere kıyasla çok daha azdır. Aslında, birçok işlev için C++'dan JavaScript'e geçmek, C++ kod tabanımda birkaç normal ifade değiştirme işlemini gerçekleştirmeyi gerektiriyordu.
Taşıma Sonuçları
Taşıma işlemiyle ilgili bu kadar. Sonraki birkaç bölümde HTML5'e özgü birkaç zorluğa değineceğim. Ancak ana mesaj şudur: Kodunuzu basit tutarsanız taşıma işlemi bir kabus değil, küçük bir baş ağrısı olacaktır.
Ses
Ses, bana (ve görünüşe göre diğer herkese) biraz sorun çıkaran bir alandı. iOS ve Android'de çeşitli ses seçenekleri (OpenSL, OpenAL) mevcuttur ancak HTML5 dünyasında durum daha karamsar görünüyordu. HTML5 Audio kullanılabilir olsa da oyunlarda kullanıldığında bazı önemli sorunlara yol açtığını tespit ettim. En yeni tarayıcılarda bile sık sık garip davranışlarla karşılaşıyordum. Örneğin, Chrome'da oluşturabileceğiniz eş zamanlı ses öğesi (kaynak) sayısıyla ilgili bir sınır var gibi görünüyor. Ayrıca, ses çaldığında bile bazen açıklanamayan bir şekilde bozuluyordu. Genel olarak biraz endişeliydim. İnternette yaptığım aramalar, neredeyse herkesin aynı sorunu yaşadığını gösteriyordu. İlk olarak bulduğum çözüm, SoundManager2 adlı bir API idi. Bu API, mümkün olduğunda HTML5 Audio'yu kullanır ve zor durumlarda Flash'a geri döner. Bu çözüm işe yaramasına rağmen, yine de hatalara ve öngörülemezliğe (saf HTML5 sesinden daha az olsa da) açıktı. Lansmandan bir hafta sonra Google'daki yardımcı kişilerden bazılarıyla konuştum. Onlar da beni Webkit'in Web Audio API'sine yönlendirdi. Başlangıçta bu API'yi kullanmayı düşünmüştüm ancak API'nin sahip olduğu gereksiz (benim için) karmaşıklık nedeniyle bundan vazgeçmiştim. Sadece birkaç ses çalmak istedim: HTML5 Audio ile bu işlem birkaç satır JavaScript'ten ibaret. Ancak Web Audio'ya kısaca göz atarken devasa (70 sayfalık) spesifikasyonu, web'deki az sayıda örnek (yeni bir API için tipik) ve spesifikasyonun hiçbir yerinde "çal", "duraklat" veya "durdur" işlevinin bulunmaması dikkatimi çekti. Google'ın endişelerimin yersiz olduğuna dair güvence vermesi üzerine API'yi tekrar inceledim. Birkaç örnek daha inceleyip biraz daha araştırma yaptıktan sonra Google'ın haklı olduğunu fark ettim. API, ihtiyaçlarımı kesinlikle karşılayabilir ve bunu diğer API'leri rahatsız eden hatalar olmadan yapabilir. Özellikle Web Audio API'yi Kullanmaya Başlama makalesi, API hakkında daha fazla bilgi edinmek istiyorsanız başvurabileceğiniz mükemmel bir kaynaktır. Asıl sorun şu ki API'yi anlayıp kullandıktan sonra bile "yalnızca birkaç ses çalmak" için tasarlanmamış bir API gibi görünüyor. Bu endişeyi gidermek için API'yi istediğim gibi kullanmama (ses çalma, duraklatma, durdurma ve sesin durumunu sorgulama) olanak tanıyan küçük bir yardımcı sınıf yazdım. Bu yardımcı sınıfa AudioClip adını verdim. Kaynak kodun tamamını Apache 2.0 lisansı kapsamında GitHub'da bulabilirsiniz. Sınıfın ayrıntılarını aşağıda ele alacağım. Öncelikle Web Audio API hakkında biraz bilgi verelim:
Web Ses Grafikleri
Web Audio API'yi HTML5 Audio öğesinden daha karmaşık (ve daha güçlü) yapan ilk özellik, sesi kullanıcıya yayınlamadan önce işleyebilmesi / karıştırabilmesidir. Güçlü olsa da ses oynatma işleminin bir grafik içermesi, basit senaryolarda işleri biraz daha karmaşık hale getirir. Web Audio API'nin gücünü göstermek için aşağıdaki grafiği inceleyin:
Yukarıdaki örnekte Web Audio API'nin gücü gösterilse de senaryomuzda bu gücün çoğuna ihtiyacım yoktu. Sadece bir ses çalmak istedim. Bunun için yine bir grafik gerekir ancak bu grafik çok basittir.
Grafikler Basit Olabilir
Web Audio API'yi HTML5 Audio öğesinden daha karmaşık (ve daha güçlü) yapan ilk özellik, sesi kullanıcıya yayınlamadan önce işleyebilmesi / karıştırabilmesidir. Güçlü olsa da ses oynatma işleminin bir grafik içermesi, basit senaryolarda işleri biraz daha karmaşık hale getirir. Web Audio API'nin gücünü göstermek için aşağıdaki grafiği inceleyin:
Yukarıda gösterilen basit grafik, bir sesi çalmak, duraklatmak veya durdurmak için gereken her şeyi yapabilir.
Ancak grafikle ilgili endişelenmenize gerek yok.
Grafiği anlamak güzel olsa da her ses çaldığımda bu işlemle uğraşmak istemiyorum. Bu nedenle, basit bir sarmalayıcı sınıfı olan "AudioClip" yazdım. Bu sınıf bu grafiği dahili olarak yönetir ancak kullanıcılara yönelik çok daha basit bir API sunar.
Bu sınıf, bir Web Audio grafiğinden ve bazı yardımcı durumdan başka bir şey değildir ancak her sesi çalmak için bir Web Audio grafiği oluşturmam gerekmesine kıyasla çok daha basit kod kullanmama olanak tanır.
// At startup time
var sound = new AudioClip("ping.wav");
// Later
sound.play();
Uygulama Ayrıntıları
Yardımcı sınıfın koduna hızlıca göz atalım: Kurucu: Kurucu, XHR kullanarak ses verilerini yükler. Burada gösterilmese de (örneği basit tutmak için) kaynak düğüm olarak bir HTML5 ses öğesi de kullanılabilir. Bu özellikle büyük örnekler için yararlıdır. Web Audio API'nin bu verileri "arraybuffer" olarak getirmemizi gerektirdiğini unutmayın. Veriler alındıktan sonra bu verilerden bir Web Audio arabelleği oluştururuz (orijinal biçiminden çalışma zamanında PCM biçimine kod çözme işlemi yaparız).
/**
* Create a new AudioClip object from a source URL. This object can be played,
* paused, stopped, and resumed, like the HTML5 Audio element.
*
* @constructor
* @param {DOMString} src
* @param {boolean=} opt_autoplay
* @param {boolean=} opt_loop
*/
AudioClip = function(src, opt_autoplay, opt_loop) {
// At construction time, the AudioClip is not playing (stopped),
// and has no offset recorded.
this.playing_ = false;
this.startTime_ = 0;
this.loop_ = opt_loop ? true : false;
// State to handle pause/resume, and some of the intricacies of looping.
this.resetTimout_ = null;
this.pauseTime_ = 0;
// Create an XHR to load the audio data.
var request = new XMLHttpRequest();
request.open("GET", src, true);
request.responseType = "arraybuffer";
var sfx = this;
request.onload = function() {
// When audio data is ready, we create a WebAudio buffer from the data.
// Using decodeAudioData allows for async audio loading, which is useful
// when loading longer audio tracks (music).
AudioClip.context.decodeAudioData(request.response, function(buffer) {
sfx.buffer_ = buffer;
if (opt_autoplay) {
sfx.play();
}
});
}
request.send();
}
Çalma: Sesi çalmak için iki adım gerekir: oynatma grafiğini ayarlamak ve grafiğin kaynağında "noteOn"un bir sürümünü çağırmak. Bir kaynak yalnızca bir kez oynatılabileceği için her oynattığımızda kaynağı/grafiği yeniden oluşturmamız gerekir.
Bu işlevin karmaşıklığının çoğu, duraklatılmış bir klibi (this.pauseTime_ > 0
) devam ettirmek için gereken koşullardan kaynaklanır. Duraklatılmış bir klibin oynatılmasını devam ettirmek için noteGrainOn
kullanırız. Bu işlev, arabelleğin bir alt bölgesinin oynatılmasına olanak tanır. Maalesef noteGrainOn
, bu senaryo için döngü işleviyle istenen şekilde etkileşime girmiyor (tüm arabelleğin değil, alt bölgenin döngüsünü oluşturuyor).
Bu nedenle, klibin kalanını noteGrainOn
ile oynatıp ardından döngü etkinleştirilerek klibi baştan başlatarak bu sorunu gidermemiz gerekiyor.
/**
* Recreates the audio graph. Each source can only be played once, so
* we must recreate the source each time we want to play.
* @return {BufferSource}
* @param {boolean=} loop
*/
AudioClip.prototype.createGraph = function(loop) {
var source = AudioClip.context.createBufferSource();
source.buffer = this.buffer_;
source.connect(AudioClip.context.destination);
// Looping is handled by the Web Audio API.
source.loop = loop;
return source;
}
/**
* Plays the given AudioClip. Clips played in this manner can be stopped
* or paused/resumed.
*/
AudioClip.prototype.play = function() {
if (this.buffer_ && !this.isPlaying()) {
// Record the start time so we know how long we've been playing.
this.startTime_ = AudioClip.context.currentTime;
this.playing_ = true;
this.resetTimeout_ = null;
// If the clip is paused, we need to resume it.
if (this.pauseTime_ > 0) {
// We are resuming a clip, so it's current playback time is not correctly
// indicated by startTime_. Correct this by subtracting pauseTime_.
this.startTime_ -= this.pauseTime_;
var remainingTime = this.buffer_.duration - this.pauseTime_;
if (this.loop_) {
// If the clip is paused and looping, we need to resume the clip
// with looping disabled. Once the clip has finished, we will re-start
// the clip from the beginning with looping enabled
this.source_ = this.createGraph(false);
this.source_.noteGrainOn(0, this.pauseTime_, remainingTime)
// Handle restarting the playback once the resumed clip has completed.
// *Note that setTimeout is not the ideal method to use here. A better
// option would be to handle timing in a more predictable manner,
// such as tying the update to the game loop.
var clip = this;
this.resetTimeout_ = setTimeout(function() { clip.stop(); clip.play() },
remainingTime * 1000);
} else {
// Paused non-looping case, just create the graph and play the sub-
// region using noteGrainOn.
this.source_ = this.createGraph(this.loop_);
this.source_.noteGrainOn(0, this.pauseTime_, remainingTime);
}
this.pauseTime_ = 0;
} else {
// Normal case, just creat the graph and play.
this.source_ = this.createGraph(this.loop_);
this.source_.noteOn(0);
}
}
}
Ses efekti olarak oynat: Yukarıdaki oynatma işlevi, ses klibinin çakışmayla birden çok kez oynatılmasına izin vermez (İkinci oynatma yalnızca klip bittiğinde veya durdurulduğunda mümkündür). Bazen bir oyun, her oynatmanın tamamlanmasını beklemeden bir sesi birçok kez çalmak ister (oyunda para toplama vb.). Bunu etkinleştirmek için AudioClip sınıfında bir playAsSFX()
yöntemi bulunur.
Aynı anda birden fazla oynatma gerçekleşebileceğinden, playAsSFX()
kaynağından gelen oynatma, AudioClip ile 1:1 olarak bağlanmaz. Bu nedenle, oynatma durdurulamaz, duraklatılamaz veya durumu sorgulanabilir. Bu şekilde çalınan bir sesi durdurmanın bir yolu olmadığından, döngü de devre dışı bırakılır.
/**
* Plays the given AudioClip as a sound effect. Sound Effects cannot be stopped
* or paused/resumed, but can be played multiple times with overlap.
* Additionally, sound effects cannot be looped, as there is no way to stop
* them. This method of playback is best suited to very short, one-off sounds.
*/
AudioClip.prototype.playAsSFX = function() {
if (this.buffer_) {
var source = this.createGraph(false);
source.noteOn(0);
}
}
Durdurma, duraklatma ve sorgu durumu: İşlevlerin geri kalanı oldukça basittir ve fazla açıklama gerektirmez:
/**
* Stops an AudioClip , resetting its seek position to 0.
*/
AudioClip.prototype.stop = function() {
if (this.playing_) {
this.source_.noteOff(0);
this.playing_ = false;
this.startTime_ = 0;
this.pauseTime_ = 0;
if (this.resetTimeout_ != null) {
clearTimeout(this.resetTimeout_);
}
}
}
/**
* Pauses an AudioClip. The offset into the stream is recorded to allow the
* clip to be resumed later.
*/
AudioClip.prototype.pause = function() {
if (this.playing_) {
this.source_.noteOff(0);
this.playing_ = false;
this.pauseTime_ = AudioClip.context.currentTime - this.startTime_;
this.pauseTime_ = this.pauseTime_ % this.buffer_.duration;
this.startTime_ = 0;
if (this.resetTimeout_ != null) {
clearTimeout(this.resetTimeout_);
}
}
}
/**
* Indicates whether the sound is playing.
* @return {boolean}
*/
AudioClip.prototype.isPlaying = function() {
var playTime = this.pauseTime_ +
(AudioClip.context.currentTime - this.startTime_);
return this.playing_ && (this.loop_ || (playTime < this.buffer_.duration));
}
Sesli Sonuç
Umarım bu yardımcı sınıf, benimle aynı ses sorunlarıyla mücadele eden geliştiricilere faydalı olur. Ayrıca, Web Audio API'nin daha güçlü özelliklerinden bazılarını eklemeniz gerekse bile bu tür bir sınıftan başlamak mantıklı bir seçimdir. Her iki durumda da bu çözüm, Bouncy Mouse'un ihtiyaçlarını karşıladı ve oyunun herhangi bir kısıtlama olmadan gerçek bir HTML5 oyunu olmasına olanak tanıdı.
Performans
JavaScript bağlantısıyla ilgili beni endişelendiren bir diğer konu da performanstı. Bağlantı noktamın 1. sürümünü tamamladıktan sonra dört çekirdekli masaüstü bilgisayarımda her şeyin sorunsuz çalıştığını gördüm. Ne yazık ki netbook veya Chromebook'larda işler pek iyi gitmedi. Bu durumda Chrome'un profilleyicisi, tüm programlarımın zamanının tam olarak nerede harcandığını göstererek beni kurtardı.
Deneyimlerim, herhangi bir optimizasyon yapmadan önce profil oluşturmanın önemini gösteriyor. Box2D fiziğinin veya oluşturma kodunun önemli bir yavaşlama kaynağı olacağını düşünüyordum. Ancak zamanımın büyük bir kısmı aslında Matrix.clone()
işlevimde harcanıyordu. Oyunumun matematik ağırlıklı yapısı nedeniyle çok sayıda matris oluşturduğumu/klonladığımı biliyordum ancak bunun darboğaz olacağını hiç düşünmemiştim. Sonunda, çok basit bir değişikliğin oyunun CPU kullanımını 3 kattan fazla azalttığı ortaya çıktı. Masaüstümde CPU kullanımı% 6-7 iken %2'ye düştü.
Bu, JavaScript geliştiricileri için yaygın bir bilgi olsa da C++ geliştiricisi olarak bu sorun beni şaşırttı. Bu nedenle biraz daha ayrıntılı bilgi vereceğim. Asıl matris sınıfım temel olarak 3x3 boyutunda bir matristi: Her biri 3 öğe dizisi içeren 3 öğe dizisi. Maalesef bu, matrisi klonlama zamanı geldiğinde 4 yeni dizi oluşturmam gerektiği anlamına geliyordu. Tek yapmam gereken değişiklik, bu verileri tek bir 9 öğe dizisine taşımak ve matematiksel işlemlerimi buna göre güncellemekti. Bu tek değişiklik, gördüğüm 3 kat CPU azaltımından tamamen sorumluydu ve bu değişiklikten sonra performansım tüm test cihazlarımda kabul edilebilir düzeye ulaştı.
Daha fazla optimizasyon
Performansım kabul edilebilir olsa da birkaç küçük sorun yaşamaya devam ediyordum. Daha ayrıntılı bir profil oluşturma işleminin ardından bunun JavaScript'in çöp toplama özelliğinden kaynaklandığını fark ettim. Uygulamam 60 fps'de çalışıyordu. Bu da her karenin oluşturulması için yalnızca 16 ms'lik bir süre olduğu anlamına geliyordu. Maalesef daha yavaş bir makinede çöp toplama işlemi başladığında bazen yaklaşık 10 ms'lik bir gecikme yaşanıyordu. Oyunun tam bir kare çizmesi neredeyse 16 ms'yi gerektirdiği için bu durum birkaç saniyede bir takılmaya neden oldu. Neden bu kadar çok çöp oluşturduğum hakkında daha iyi bir fikir edinmek için Chrome'un yığın profili analiz aracını kullandım. Maalesef, gereksiz verilerin büyük çoğunluğunun (%70'den fazlası) Box2D tarafından oluşturulduğu ortaya çıktı. Javascript'te gereksiz kodları kaldırmak zor bir iş. Box2D'yi yeniden yazmak da söz konusu değildi. Bu yüzden kendimi köşeye sıkıştırdığımı fark ettim. Neyse ki, en eski numaralardan birini kullanabiliyordum: 60 fps'ye ulaşamıyorsanız 30 fps'de çalıştırın. 30 fps'de tutarlı bir şekilde oynamanın, 60 fps'de takılmalı bir şekilde oynamaktan çok daha iyi olduğu konusunda genel bir fikir birliği vardır. Aslında oyunun 30 fps'de çalıştığıyla ilgili tek bir şikayet veya yorum almadım (iki sürümü yan yana karşılaştırmadığınız sürece bunu anlamak gerçekten zor). Kare başına 16 ms daha fazla süre, kötü bir çöp toplama durumunda bile kareyi oluşturmak için bolca zamana sahip olduğum anlamına geliyordu. 30 fps'de çalışma, kullandığım zamanlama API'si (WebKit'in mükemmel requestAnimationFrame) tarafından açıkça etkinleştirilmese de çok basit bir şekilde yapılabilir. Açık bir API kadar zarif olmasa da RequestAnimationFrame'in aralığının monitörün VSYNC'ine (genellikle 60 fps) hizalandığı bilinerek 30 fps elde edilebilir. Bu nedenle, diğer tüm geri aramaları yoksaymamız yeterlidir. Temel olarak, "RequestAnimationFrame" her tetiklendiğinde çağrılan bir geri çağırma "Tick" işleviniz varsa bu işlem aşağıdaki gibi gerçekleştirilebilir:
var skip = false;
function Tick() {
skip = !skip;
if (skip) {
return;
}
// OTHER CODE
}
Daha fazla dikkatli olmak istiyorsanız bilgisayarın VSYNC'inin başlangıçta 30 fps'de veya altında olup olmadığını kontrol etmeniz ve bu durumda atlamayı devre dışı bırakmanız gerekir. Ancak test ettiğim hiçbir masaüstü/dizüstü bilgisayar yapılandırmasında bu sorunla karşılaşmadım.
Dağıtım ve Para Kazanma
Bouncy Mouse'un Chrome bağlantısında beni şaşırtan son bir alan da para kazanma oldu. Bu projeye başlarken HTML5 oyunlarını, yeni teknolojileri öğrenmek için ilginç bir deneme olarak düşündüm. Ancak bu portun çok geniş bir kitleye ulaşacağını ve para kazanma potansiyelinin önemli olduğunu fark etmemiştim.
Bouncy Mouse, Ekim ayının sonunda Chrome Web Mağazası'nda kullanıma sunuldu. Chrome Web Mağazası'nda kullanıma sunarak keşfedilebilirlik, topluluk etkileşimi, sıralamalar ve mobil platformlarda alıştığım diğer özellikler için mevcut bir sistemden yararlanabildim. Mağazamın ne kadar geniş bir kitleye ulaştığını görünce şaşırdım. Uygulamayı kullanıma sunmamızın üzerinden bir ay geçmeden dört yüz bine yakın yükleme elde ettik ve topluluk etkileşiminden (hata bildirme, geri bildirim) yararlanmaya başladık. Beni şaşırtan bir diğer şey de web uygulamalarının para kazanma potansiyeliydi.
Bouncy Mouse'ta para kazanmanın tek ve basit bir yolu vardır: oyun içeriğinin yanında bir banner reklam. Ancak oyunun geniş erişimi göz önüne alındığında, bu banner reklamın önemli miktarda gelir sağlayabildiğini fark ettim. Uygulama, en yoğun döneminde en başarılı platformum olan Android'e kıyasla benzer bir gelir elde etti. Buna katkıda bulunan faktörlerden biri, HTML5 sürümünde gösterilen daha büyük AdSense reklamlarının, Android'de gösterilen daha küçük AdMob reklamlarına kıyasla gösterim başına önemli ölçüde daha yüksek gelir sağlamasıdır. Bununla birlikte, HTML5 sürümündeki banner reklam, Android sürümüne kıyasla çok daha az müdahalecidir ve daha temiz bir oyun deneyimi sunar. Genel olarak bu sonuçtan çok memnun kaldım.

Oyundan elde edilen kazançlar beklenenden çok daha iyi olsa da Chrome Web Mağazası'nın erişiminin Android Market gibi daha gelişmiş platformların erişiminin altında kaldığını belirtmek isteriz. Bouncy Mouse, kısa sürede Chrome Web Mağazası'ndaki en popüler 9. oyun haline geldi ancak ilk sürümden bu yana siteye gelen yeni kullanıcıların oranı önemli ölçüde yavaşladı. Bununla birlikte, oyun hâlâ istikrarlı bir şekilde büyüyor ve platformun nasıl gelişeceğini görmek için sabırsızlanıyorum.
Sonuç
Bouncy Mouse'u Chrome'a taşıma işleminin beklediğimden çok daha sorunsuz geçtiğini söyleyebilirim. Ses ve performansla ilgili bazı küçük sorunlar dışında, Chrome'un mevcut bir akıllı telefon oyunu için mükemmel bir platform olduğunu gördüm. Bu deneyimden uzak duran tüm geliştiricilerin denemesini öneririm. Hem taşıma sürecinden hem de HTML5 oyunu sayesinde ulaşabildiğim yeni oyun kitlesinden çok memnunum. Sorularınız olursa bana e-posta gönderebilirsiniz. Dilerseniz aşağıdan yorum da bırakabilirsiniz. Yorumları düzenli olarak kontrol etmeye çalışırım.