Giriş
HTML5, tarayıcıda modern, duyarlı ve güçlü web uygulamaları oluşturmak için birçok yararlı API sağlamıştır. Bu harika, ancak asıl amacınız oyun oluşturmak ve oynamak. Neyse ki HTML5, eklenti kullanmadan oyunları doğrudan tarayıcınızda sunmak için Tuval gibi API'leri ve güçlü JavaScript motorlarını kullanan yeni bir oyun geliştirme çağına da öncülük etti.
Bu makalede, HTML5 oyununuz için basit bir öğe yönetimi bileşeni oluşturma konusunda size yol gösterilmektedir. Öğe yöneticisi olmadan oyununuz, bilinmeyen indirme sürelerini ve eşzamansız resim yüklemeyi telafi etmekte zorlanır. HTML5 oyunlarınız için basit bir öğe yöneticisi örneğini görmek üzere bu makaleyi inceleyin.
Sorun
HTML5 oyunları, resim veya ses gibi öğelerinin oynatıcının yerel makinesinde olacağını varsayamaz. Bunun nedeni, HTML5 oyunlarının HTTP üzerinden indirilen öğelerle bir web tarayıcısında oynatılmasını gerektirmesidir. Ağın dahil olması nedeniyle tarayıcı, oyunun öğelerinin ne zaman indirilip kullanılabileceğinden emin değildir.
Bir resmi web tarayıcısında programatik olarak yüklemenin temel yolu aşağıdaki koddur:
var image = new Image();
image.addEventListener("success", function(e) {
// do stuff with the image
});
image.src = "/some/image.png";
Şimdi de oyun başladığında yüklenmesi ve gösterilmesi gereken yüz resim olduğunu hayal edin. 100 resmin hepsinin hazır olduğunu nasıl anlarsınız? Tümünü başarıyla yüklediniz mi? Maç ne zaman başlayacak?
Çözüm
Öğelerin sıraya alınmasını bir öğe yöneticisine bırakın ve her şey hazır olduğunda oyuna geri bildirimde bulunun. Öğe yöneticisi, öğeleri ağ üzerinden yükleme mantığını genelleştirir ve durumu kontrol etmenin kolay bir yolunu sağlar.
Basit öğe yöneticimiz için aşağıdaki koşullar geçerlidir:
- indirme işlemlerini sıraya alma
- indirme işlemlerini başlatma
- başarı ve başarısızlığı takip edebilirsiniz.
- Her şey tamamlandığında sinyal gönderme
- Öğelerin kolayca geri alınabilmesi
Sıraya ekleme
İlk şart, indirme işlemlerini sıraya koymaktır. Bu tasarım, ihtiyacınız olan öğeleri gerçekten indirmeden bildirmenize olanak tanır. Örneğin, bir oyun düzeyindeki tüm öğeleri bir yapılandırma dosyasında tanımlamak istiyorsanız bu özellik yararlı olabilir.
Oluşturucu ve sıraya ekleme kodunun görünümü:
function AssetManager() {
this.downloadQueue = [];
}
AssetManager.prototype.queueDownload = function(path) {
this.downloadQueue.push(path);
}
İndirme işlemini başlatma
İndirilecek tüm öğeleri sıraya ekledikten sonra öğe yöneticisinden her şeyi indirmeye başlamasını isteyebilirsiniz.
Neyse ki web tarayıcısı indirmeleri paralelleştirebilir. Genellikle ana makine başına 4 bağlantıya kadar paralelleştirme yapılabilir. Öğe indirme hızını artırmanın bir yolu, öğe barındırma için çeşitli alan adları kullanmaktır. Örneğin, her şeyi assets.example.com adresinden yayınlamak yerine assets1.example.com, assets2.example.com, assets3.example.com vb. adresleri kullanmayı deneyin. Bu alan adlarının her biri aynı web sunucusu için CNAME olsa bile web tarayıcısı bunları ayrı sunucular olarak görür ve öğe indirme için kullanılan bağlantı sayısını artırır. Web Sitenizi Hızlandırmayla İlgili En İyi Uygulamalar başlıklı makaledeki Bileşenleri Alanlar Arasında Bölme bölümünden bu teknik hakkında daha fazla bilgi edinebilirsiniz.
İndirme başlatma yöntemimize downloadAll()
denir. Zaman içinde bu özelliği geliştireceğiz. Şimdilik indirme işlemini başlatan ilk mantık budur.
AssetManager.prototype.downloadAll = function() {
for (var i = 0; i < this.downloadQueue.length; i++) {
var path = this.downloadQueue[i];
var img = new Image();
var that = this;
img.addEventListener("load", function() {
// coming soon
}, false);
img.src = path;
}
}
Yukarıdaki kodda görebileceğiniz gibi downloadAll()
, downloadQueue üzerinden iterasyon gerçekleştirip yeni bir Image nesnesi oluşturur. Yükleme etkinliği için bir etkinlik işleyici eklenir ve resmin src özelliği ayarlanır. Bu, gerçek indirme işlemini tetikler.
Bu yöntemle indirme işlemlerini başlatabilirsiniz.
Başarı ve başarısızlığı izleme
Maalesef her şey her zaman mükemmel şekilde çalışmadığı için bir diğer şart da hem başarıları hem de başarısızlıkları takip etmektir. Kod şu anda yalnızca başarıyla indirilen öğeleri izler. Hata etkinliği için bir etkinlik dinleyici ekleyerek hem başarılı hem de başarısız senaryoları yakalayabilirsiniz.
AssetManager.prototype.downloadAll = function(downloadCallback) {
for (var i = 0; i < this.downloadQueue.length; i++) {
var path = this.downloadQueue[i];
var img = new Image();
var that = this;
img.addEventListener("load", function() {
// coming soon
}, false);
img.addEventListener("error", function() {
// coming soon
}, false);
img.src = path;
}
}
Öğe yöneticimizin, ne kadar başarı ve başarısızlıkla karşılaştığımızı bilmesi gerekir. Aksi takdirde oyunun ne zaman başlayabileceğini asla bilemez.
İlk olarak, oluşturucudaki sayaçları nesneye ekleyeceğiz. Böylece, nesne şu şekilde görünür:
function AssetManager() {
<span class="highlight"> this.successCount = 0;
this.errorCount = 0;</span>
this.downloadQueue = [];
}
Ardından, etkinlik işleyicilerindeki sayaçları artırın. Artık şu şekilde görünüyorlar:
img.addEventListener("load", function() {
<span class="highlight">that.successCount += 1;</span>
}, false);
img.addEventListener("error", function() {
<span class="highlight">that.errorCount += 1;</span>
}, false);
Öğe yöneticisi artık hem başarıyla yüklenen hem de yüklenemeyen öğeleri izler.
İşlem Tamamlandığında Sinyal Gönderme
Oyun, öğelerini indirme sırasına ekledikten ve öğe yöneticisinden tüm öğeleri indirmesini istedikten sonra, tüm öğeler indirildiğinde oyuna bilgi verilmelidir. Oyunun öğelerin indirilip indirilmediğini tekrar tekrar sorması yerine öğe yöneticisi oyuna sinyal gönderebilir.
Öğe yöneticisinin öncelikle her öğenin ne zaman bittiğini bilmesi gerekir. Şimdi isDone yöntemini ekleyeceğiz:
AssetManager.prototype.isDone = function() {
return (this.downloadQueue.length == this.successCount + this.errorCount);
}
Öğe yöneticisi, successCount + errorCount değerini downloadQueue boyutuyla karşılaştırarak her öğenin başarıyla tamamlanıp tamamlanmadığını veya bir tür hata içerip içermediğini bilir.
Elbette, işlemin tamamlanıp tamamlanmadığını bilmek işin yalnızca yarısıdır. Öğe yöneticisinin bu yöntemi kontrol etmesi de gerekir. Aşağıdaki kodda gösterildiği gibi bu kontrolü her iki etkinlik işleyicimizin içine de ekleyeceğiz:
img.addEventListener("load", function() {
console.log(this.src + ' is loaded');
that.successCount += 1;
if (that.isDone()) {
// ???
}
}, false);
img.addEventListener("error", function() {
that.errorCount += 1;
if (that.isDone()) {
// ???
}
}, false);
Sayaçlar artırıldıktan sonra, sıradaki son öğe olup olmadığını kontrol ederiz. Öğe yöneticisi gerçekten indirmeyi tamamladıysa tam olarak ne yapmalıyız?
Öğe yöneticisi tüm öğeleri indirdiyse elbette geri çağırma yöntemini çağırırız. downloadAll()
değerini değiştirip geri çağırma için bir parametre ekleyelim:
AssetManager.prototype.downloadAll = function(downloadCallback) {
...
Etkinlik işleyicilerimizin içinde downloadCallback yöntemini çağıracağız:
img.addEventListener("load", function() {
that.successCount += 1;
if (that.isDone()) {
downloadCallback();
}
}, false);
img.addEventListener("error", function() {
that.errorCount += 1;
if (that.isDone()) {
downloadCallback();
}
}, false);
Öğe yöneticisi nihayet son şarta hazırdır.
Öğeleri kolayca alma
Oyun başlatılabileceğine dair sinyal aldıktan sonra oyun, resimleri oluşturmaya başlar. Öğe yöneticisi, öğeleri indirip izlemekten değil, aynı zamanda oyuna sunmaktan da sorumludur.
Son şartımız bir tür getAsset yöntemi gerektiriyor. Bu nedenle, şimdi bunu ekleyeceğiz:
AssetManager.prototype.getAsset = function(path) {
return this.cache[path];
}
Bu önbellek nesnesi, artık şu şekilde görünen oluşturucuda başlatılır:
function AssetManager() {
this.successCount = 0;
this.errorCount = 0;
this.cache = {};
this.downloadQueue = [];
}
Önbellek, aşağıda gösterildiği gibi downloadAll()
öğesinin sonunda doldurulur:
AssetManager.prototype.downloadAll = function(downloadCallback) {
...
img.addEventListener("error", function() {
that.errorCount += 1;
if (that.isDone()) {
downloadCallback();
}
}, false);
img.src = path;
<span class="highlight">this.cache[path] = img;</span>
}
}
Bonus: Hata Düzeltmesi
Hatayı fark ettiniz mi? Yukarıda belirtildiği gibi, isDone yöntemi yalnızca load veya error etkinlikleri tetiklendiğinde çağrılır. Ancak öğe yöneticisinin indirme için sıraya alınmış öğesi yoksa ne olur? isDone yöntemi hiçbir zaman tetiklenmez ve oyun hiçbir zaman başlamaz.
downloadAll()
öğesine aşağıdaki kodu ekleyerek bu senaryoya uyum sağlayabilirsiniz:
AssetManager.prototype.downloadAll = function(downloadCallback) {
if (this.downloadQueue.length === 0) {
downloadCallback();
}
...
Sıralamadaki öğe yoksa geri arama hemen yapılır. Hata düzeltildi.
Örnek Kullanım
Bu öğe yöneticisini HTML5 oyununuzda kullanmak oldukça kolaydır. Kitaplığı kullanmanın en temel yolu şunlardır:
var ASSET_MANAGER = new AssetManager();
ASSET_MANAGER.queueDownload('img/earth.png');
ASSET_MANAGER.downloadAll(function() {
var sprite = ASSET_MANAGER.getAsset('img/earth.png');
ctx.drawImage(sprite, x - sprite.width/2, y - sprite.height/2);
});
Yukarıdaki kodda şunlar gösterilmektedir:
- Yeni bir öğe yöneticisi oluşturur
- İndirilecek öğeleri sıraya ekleme
- İndirme işlemini
downloadAll()
ile başlatın - Geri çağırma işlevini çağırarak öğelerin hazır olduğunu belirtin
getAsset()
ile öğeleri alma
Geliştirilmesi gereken alanlar
Oyununuzu geliştirirken bu basit öğe yöneticisinin yetersiz kalacağını tahmin ediyoruz. Ancak bu yöneticinin başlangıç için faydalı olacağını umuyoruz. Gelecekte kullanıma sunulacak özelliklerden bazıları şunlardır:
- Hangi öğede hata olduğunu belirtme
- İlerleme durumunu gösteren geri aramalar
- File System API'den öğe alma
Lütfen iyileştirmeleri, çatalları ve kod bağlantılarını aşağıdaki yorumlara gönderin.
Tam Kaynak
Bu öğe yöneticisinin ve soyutlandığı oyunun kaynağı, Apache Lisansı kapsamında açık kaynaktır ve Bad Aliens GitHub hesabında bulunabilir. Bad Aliens oyunu, HTML5 uyumlu tarayıcınızda oynanabilir. Bu oyun, Super Browser 2 Turbo HD Remix: Introduction to HTML5 Game Development (Super Browser 2 Turbo HD Remix: HTML5 Oyun Geliştirmeye Giriş) başlıklı Google IO konuşmamın konusuydu (sunu, video).
Özet
Çoğu oyunda bir tür öğe yöneticisi bulunur ancak HTML5 oyunlarında öğeleri ağ üzerinden yükleyen ve hataları işleyen bir öğe yöneticisi gerekir. Bu makalede, kullanması ve bir sonraki HTML5 oyununuza uyarlaması kolay olması gereken basit bir öğe yöneticisi açıklanmaktadır. İyi eğlenceler. Düşüncelerinizi aşağıdaki yorumlar bölümünde bizimle paylaşın. Teşekkürler!