Wprowadzenie
HTML5 udostępnia wiele przydatnych interfejsów API do tworzenia nowoczesnych, responsywnych i wydajnych aplikacji internetowych w przeglądarce. To świetnie, ale naprawdę chcesz tworzyć i grać w gry. Na szczęście HTML5 wprowadził też nową erę tworzenia gier, która wykorzystuje interfejsy API takie jak Canvas i potężne silniki JavaScript, aby umożliwić granie bezpośrednio w przeglądarce bez konieczności instalowania wtyczek.
Z tego artykułu dowiesz się, jak utworzyć prosty komponent zarządzania zasobami do gry HTML5. Bez menedżera komponentów trudno będzie zrekompensować nieznane czasy pobierania i asyncjoniczne wczytywanie obrazów. Zobacz przykład prostego menedżera komponentów w przypadku gier HTML5.
Problem
Gry w HTML5 nie mogą zakładać, że ich zasoby, takie jak obrazy czy dźwięk, znajdują się na komputerze gracza, ponieważ gry w HTML5 są przeznaczone do odtwarzania w przeglądarce z zasobami pobieranymi przez HTTP. Ponieważ w tym przypadku jest zaangażowana sieć, przeglądarka nie wie, kiedy zasoby gry zostaną pobrane i staną się dostępne.
Podstawowy sposób wczytywania obrazu w przeglądarce za pomocą kodu wygląda tak:
var image = new Image();
image.addEventListener("success", function(e) {
// do stuff with the image
});
image.src = "/some/image.png";
Teraz wyobraź sobie, że masz 100 obrazów, które muszą zostać załadowane i wyświetlone po uruchomieniu gry. Jak sprawdzić, czy wszystkie 100 obrazów są gotowe? Czy wszystkie zostały załadowane? Kiedy powinna się rozpocząć gra?
Rozwiązanie
Pozwól menedżerowi zasobów ustawić kolejność zasobów i poinformuj zespół gry, gdy wszystko będzie gotowe. Menedżer zasobów uogólnia logikę wczytywania zasobów przez sieć i ułatwia sprawdzanie stanu.
Nasz prosty menedżer zasobów ma te wymagania:
- kolejka pobierania;
- rozpoczęcie pobierania;
- śledzić powodzenie i niepowodzenie
- sygnał, gdy wszystko będzie gotowe
- łatwe pobieranie zasobów.
Umieszczam w kolejce
Pierwszym wymaganiem jest ustawienie kolejki pobierania. Dzięki temu możesz zadeklarować potrzebne komponenty bez ich pobierania. Może to być przydatne, jeśli np. chcesz zadeklarować wszystkie zasoby poziomu gry w pliku konfiguracyjnym.
Kod konstruktora i kolejkowania wygląda tak:
function AssetManager() {
this.downloadQueue = [];
}
AssetManager.prototype.queueDownload = function(path) {
this.downloadQueue.push(path);
}
Rozpocznij pobieranie
Gdy utworzysz kolejkę wszystkich zasobów do pobrania, możesz poprosić menedżera zasobów o rozpoczęcie pobierania.
Przeglądarka internetowa może równolegle pobierać pliki, co zwykle oznacza do 4 połączenia na hosta. Jednym ze sposobów przyspieszenia pobierania zasobów jest użycie zakresu nazw domen do hostowania zasobów. Zamiast wyświetlać wszystkie zasoby z adresu assets.example.com, użyj adresów assets1.example.com, assets2.example.com, assets3.example.com itd. Nawet jeśli każda z tych nazw domen jest po prostu rekordem CNAME dla tego samego serwera WWW, przeglądarka internetowa widzi je jako oddzielne serwery i zwiększa liczbę połączeń używanych do pobierania zasobów. Więcej informacji o tej metodzie znajdziesz w artykule Rozdzielanie komponentów na domeny w sekcji „Sprawdzone metody przyspieszania działania witryny”.
Nasza metoda inicjowania pobierania nosi nazwę downloadAll()
. Z czasem go rozbudujemy. Na razie podaję pierwszą logikę, która uruchamia pobieranie.
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;
}
}
Jak widać w powyższym kodzie, funkcja downloadAll()
po prostu iteruje po kolejce downloadQueue i tworzy nowy obiekt Image. Dodano detektor zdarzenia wczytywania i ustawiono atrybut src obrazu, co powoduje faktyczne pobieranie.
Dzięki tej metodzie możesz rozpocząć pobieranie.
Śledzenie powodzenia i niepowodzenia
Kolejnym wymaganiem jest śledzenie zarówno sukcesów, jak i porażek, ponieważ niestety nie wszystko zawsze działa idealnie. Dotychczas kod śledzi tylko pobrane zasoby. Dodając detektor zdarzeń dla zdarzenia błędu, możesz rejestrować zarówno scenariusze powodzenia, jak i niepowodzenia.
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;
}
}
Nasz menedżer zasobów musi wiedzieć, ile mieliśmy sukcesów i porażek, inaczej nigdy nie dowie się, kiedy można zacząć rozgrywkę.
Najpierw dodamy liczniki do obiektu w konstruktorze, który teraz wygląda tak:
function AssetManager() {
<span class="highlight"> this.successCount = 0;
this.errorCount = 0;</span>
this.downloadQueue = [];
}
Następnie zwiększaj liczniki w detektorach zdarzeń, które wyglądają teraz tak:
img.addEventListener("load", function() {
<span class="highlight">that.successCount += 1;</span>
}, false);
img.addEventListener("error", function() {
<span class="highlight">that.errorCount += 1;</span>
}, false);
Menedżer komponentów śledzi teraz zarówno komponenty załadowane, jak i te, które nie zostały załadowane.
Sygnalizacja po zakończeniu
Gdy gra umieściła zasoby w kolejce do pobrania i poprosiła menedżera zasobów o pobranie wszystkich zasobów, musi zostać poinformowana, kiedy wszystkie zasoby zostaną pobrane. Zamiast wielokrotnego pytania, czy zasoby zostały pobrane, menedżer zasobów może przekazać tę informację grze.
Menedżer zasobów musi najpierw wiedzieć, kiedy każdy zasób zostanie ukończony. Dodajemy teraz metodę isDone:
AssetManager.prototype.isDone = function() {
return (this.downloadQueue.length == this.successCount + this.errorCount);
}
Porównując liczbę successCount + errorCount z wielkością kolejki downloadQueue, menedżer zasobów wie, czy każdy zasób został przetworzony pomyślnie, czy wystąpił jakiś błąd.
Oczywiście samo sprawdzenie, czy to zostało zrobione, to dopiero połowa sukcesu. Menedżer zasobów musi też sprawdzić tę metodę. Dodamy tę weryfikację do obu naszych metod obsługi zdarzeń, jak widać w poniższym kodzie:
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);
Gdy liczniki zostaną zwiększone, sprawdzimy, czy był to ostatni zasób w naszej kolejce. Jeśli menedżer komponentów został już pobrany, co dokładnie powinniśmy zrobić?
Gdy menedżer zasobów zakończy pobieranie wszystkich zasobów, wywołamy oczywiście metodę wywołania zwrotnego. Zmień wartość parametru downloadAll()
i dodaj parametr wywołania zwrotnego:
AssetManager.prototype.downloadAll = function(downloadCallback) {
...
Metodę downloadCallback wywołamy w detektorach zdarzeń:
img.addEventListener("load", function() {
that.successCount += 1;
if (that.isDone()) {
downloadCallback();
}
}, false);
img.addEventListener("error", function() {
that.errorCount += 1;
if (that.isDone()) {
downloadCallback();
}
}, false);
Menedżer komponentów jest już gotowy do spełnienia ostatniego wymagania.
Łatwe pobieranie zasobów
Gdy gra otrzyma sygnał, że może się uruchomić, zacznie renderować obrazy. Menedżer zasobów odpowiada nie tylko za pobieranie i śledzenie zasobów, ale też za ich udostępnianie w grze.
Nasz ostatni wymóg zakłada użycie metody getAsset, więc dodamy ją teraz:
AssetManager.prototype.getAsset = function(path) {
return this.cache[path];
}
Obiekt pamięci podręcznej jest inicjowany w konstruktoorze, który teraz wygląda tak:
function AssetManager() {
this.successCount = 0;
this.errorCount = 0;
this.cache = {};
this.downloadQueue = [];
}
Pamięć podręczna jest wypełniana na końcu downloadAll()
, jak pokazano poniżej:
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: poprawka błędu
Czy udało Ci się znaleźć błąd? Jak już wspomniano, metoda isDone jest wywoływana tylko wtedy, gdy są wywoływane zdarzenia ładowania lub błędu. Co jednak, jeśli menedżer zasobów nie ma żadnych zasobów w kolejce do pobrania? Metoda isDone nigdy nie jest wywoływana, a gra nigdy się nie rozpoczyna.
Aby uwzględnić ten scenariusz, dodaj do pliku downloadAll()
ten kod:
AssetManager.prototype.downloadAll = function(downloadCallback) {
if (this.downloadQueue.length === 0) {
downloadCallback();
}
...
Jeśli w kolejce nie ma żadnych zasobów, wywołanie zwrotne zostanie wywołane natychmiast. Błąd został naprawiony.
Przykład użycia
Korzystanie z tego menedżera zasobów w grze HTML5 jest bardzo proste. Oto najprostszy sposób korzystania z biblioteki:
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);
});
Powyższy kod pokazuje:
- Tworzy nowego menedżera komponentów.
- kolejkowanie komponentów do pobrania,
- Rozpocznij pobieranie za
downloadAll()
- Za pomocą wywołania funkcji wywołania zwrotnego sygnalizuj, że zasoby są gotowe.
- Pobieranie komponentów za pomocą
getAsset()
Obszary wymagające poprawy
Podczas tworzenia gry z pewnością będziesz potrzebować bardziej zaawansowanego narzędzia do zarządzania zasobami, ale mam nadzieję, że ta prosta wersja pozwoli Ci zacząć. Do przyszłych funkcji należą:
- sygnalizacja, który zasób zawierał błąd;
- wywołania zwrotne wskazujące postęp,
- pobieranie komponentów z interfejsu File System API
W komentarzach poniżej opublikuj ulepszenia, zmiany i linki do kodu.
Full Source
Kod źródłowy tego menedżera zasobów i gra, z której pochodzi, są dostępne na licencji Apache i można je znaleźć na koncie Bad Aliens na GitHubie. W grę Bad Aliens możesz grać w zgodn z tą przeglądarką w wersji HTML5. Ta gra była tematem mojego wystąpienia na konferencji Google IO zatytułowanego Super Browser 2 Turbo HD Remix: Introduction to HTML5 Game Development (slajdy, wideo).
Podsumowanie
Większość gier ma jakiś rodzaj menedżera zasobów, ale gry HTML5 wymagają menedżera zasobów, który wczytuje zasoby przez sieć i rozpatruje błędy. W tym artykule opisaliśmy prosty menedżer zasobów, który powinien być łatwy w użyciu i dostosować do Twojej następnej gry HTML5. Miłej zabawy! Podziel się z nami swoją opinią w komentarzach poniżej. Dziękujemy!