Tworzenie gier w JavaScript – Object Pooling. Teoria.

W dzisiejszych czasach w gry webowe zagrać można na różnych urządzeniach, nie tylko na mocnych komputerach stacjonarnych ale też na znacznie skromniejszych urządzeniach mobilnych. Jako developerzy chcemy aby gra dotarła do jak największej grupy odbiorców, dlatego wydajność jest bardzo ważna.

W tym wpisie przedstawię prostą mechanikę, która pozwoli na znaczne polepszenie wydajności każdej gry. Object Pooling, bo o nim mowa to wręcz podstawa, bez której nie można mówić o wydajnie działającej grze. Wiem bo moje własne (też przedstawiane na blogu) gry cierpiały z powodu brak tego mechanizmu.

javascript tajniki programowania gier

Jak sugeruje tytuł posta, tym razem zajmę się głównie teorią. Praktyczny przykład wykorzystania Object Poolingu przedstawię w przyszłym wpisie. Znajomość idei Object Poolingu przyda się każdemu developerowi, nie tylko tym, którzy tworzą gry w JavaScripcie. Rozwiązanie to jest dość uniwersalne i świetnie sprawdza się w każdym języku, chociaż implementacje, oczywiście będą się różnić.

Czym Jest Object Pooling?

Zacznę na bardzo wysokim poziomie abstrakcji. Object pooling, czyli czynność gromadzenia obiektów, to technika pozwalająca na dobre zarządzanie pamięcią programu.

W grach komputerowych prawie zawsze powstaje bardzo dużo instancji obiektów. Mogą to być przeciwnicy, pociski, wybuchy, wszystko to czego w grach zazwyczaj jest bardzo dużo.

Wiele z tych obiektów pojawia się na ekranie dosłownie na chwilę po czym znika bezpowrotnie. W takiej sytuacji pamięć programu bardzo szybko zostaje wypełniona dużą ilością referencji, nierzadko do nieistniejących już obiektów. To oczywiście może bardzo źle wpłynąć na wydajność gry.

Object pooling służy do ustabilizowania takich sytuacji i pozwala na praktycznie pełną kontrolę nad tworzonymi obiektami. W tym rozwiązaniu od samego początku mamy już dostępny pewien zbiór obiektów, po prostu nie są one oflagowane jako aktywne. Dopiero gdy zajdzie taka potrzeba, program pobiera nieaktywny obiekt ze zbioru i dodaje go do gry.

Jeśli obiekt „zginie” w grze, nie jest usuwany ze zbioru, po prostu jest deaktywowany. Główny program wie, aby takiego obiektu nie rendereować ani nie aktualizować.

I to praktycznie cała idea stojąca za Object Poolingiem. Jeśli wydaje się proste, to dobrze, bo to nic skomplikowanego 🙂 .

Jak to się ma do JavaScriptu?

Ktoś może powiedzieć, że przecież w JavaScripcie, to nie jest potrzebne. Ten język ma przecież wbudowany Garbage Collection (zbieranie śmieci). System ten sam opróżnia pamięć z obiektów, do których wszystkie referencje zostały usunięte. Sam tak przez dłuższy czas myślałem, co widać w moich grach, opisywanych tu na blogu (szczególnie w platformówce na DSP, gry phaserowe mają własny Object Pooling – grupy).

GC to świetny system, który sprawdza się dobrze jeżeli pracujemy z małą ilością obiektów w aplikacji webowej. Jeśli chodzi o gry, sprawa nie jest taka prosta. W grach liczy się każda sekunda, kilka zagubionych klatek może spowodować nieprzyjemny lag. A problem z GC polega na tym, że nie mamy nigdy pewności kiedy zacznie działać. Zazwyczaj dzieje się to dopiero w momencie, kiedy pamięć jest już dość konkretnie zapełniona.

Do tego należy dodać jeszcze fakt, że gdy działa GC, wszystkie inne czynności programu zatrzymują się i ruszają dopiero, gdy pamięć zostanie oczyszczona – spowolnienie gry gwarantowane.

Jak wprowadzić Object Pooling do JS?

Dobra, to była teoria na dość wysokim poziomie abstrakcji, czas zejść trochę bliżej ziemi. Wprawdzie nie przedstawię dziś żadnego kodu, ale aby cała idea stałą się bardziej namacalna, postaram się opisać ją, ilustrując konkretnymi sytuacjami.

Oczywiście już dawno zdałem sobie sprawę, że w moich grach potrzebna jest jakaś forma grupowania obiektów. Do tej pory używałem po prostu tablic. Przykładowo, tablica enemies zawierała wszystkie obiekty wrogów.

W czasie trwania gry, game loop sprawdzał każdy obiekt w tablicy. Jeżeli któryś z nich oflagowany byłby jako martwy, program usuwał go używając metody slice. Wszystko fajnie, ale w ten sposób usuwam referencje nie tylko do obiektu martwego przeciwnika, ale też do obiektu tablicy, która zawiera tego przeciwnika, przy okazji tworzy się instancja tablicy nie posiadająca tego elementu… a do pamięci trafiają kolejne obiekty bez referencji. Garbage Collector będzie miał co co robić.

Przy okazji w tym samym obiegu game loopa, w grze pojawili się dwaj nowi przeciwnicy. Oczywiście dodaję ich do tablicy enemies używając metody push. To kolejne dwa obiekty tablicy bez referencji. I tak dalej…

Nie trzeba specjalnie długo studiować tego przykładu aby zobaczyć marnotrawstwo pamięci jakie tu zachodzi. Używając Object Pooling, można odtworzyć tę samą sytuację, nie wysyłając do garbage collectora ani jednego obiektu! Wystarczyłoby manipulować odpowiednio istniejącymi obiektami.

Oczywiście dochodzi jeszcze zagadnienie rozmiaru zbioru obiektów, ale i to da się w miarę łatwo rozwiązać. To jednak pokażę dokładnie już we wpisie praktycznym.

Na dziś to wszystko. W kolejnym poście przedstawię pełny przykład wraz z kodem, w którym dokładnie opiszę implementację Object Poolingu. Jeśli chcesz być na bieżąco z postami na blogu zachęcam do polubienia mojej strony na facebooku. Zawsze zamieszczam tam informacje o wszystkich nowościach. Jest to też dobre miejsce na kontakt ze mną. Na wszystkie pytania zawsze odpowiem :).

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *