Optymalizacja gier w GM

Klikipedia - klikowa encyklopedia
Skocz do: nawigacji, wyszukiwarki
Autor.jpg
Autorem tego artykułu jest
morty

Aplikacje stworzone w programie Game Maker są bardzo szybkie. Kiedy jednak stopień komplikacji i złożoności poszczególnych elementów okażuje się być bardzo wysoki, konieczna może być optymalizacja gry. Ten artykuł pokazuje, co może powodować zwalnianie gry i jak temu zapobiegać.

Racjonalne używanie obiektów

Najważniejszym elementem w grach 2d jest oszczędne podejście do zużywanych zasobów. Każdy obiekt znajdujący się na ekranie zajmuje pewną ilość miejsca w pamięci. Gdziekolwiek jest to możliwe należy dążyć do zniwelowania liczby aktywnych obiektów. Przykładowo, tworząc grę o dużym polu gry, z którego w danym momencie widoczna jest niewielka część (np. w przesuwanych grach platformowych) wiele obiektów leży poza granicami widocznej części. Obiekty te nadal wymagają ciągłego sprawdzania zdefiniowanych zdarzeń i wykonywania zawartego w nich kodu. Co więcej, sprawdzanie kolizji również obejmuje te obiekty. Zazwyczaj jest to marnotastwo mocy obliczeniowej - często obiekty znajdujące się za ekranem można np. wstrzymać na czas gdy nie są widoczne, i aktywować kiedy są potrzebne - przyspieszy to znacznie działanie gry.

W tym celu w Game Makerze istnieją funkcje, służące do aktywacji i deaktywacji obiektów. Nieaktywny obiekt w pewnym sensie nie istnieje dla gry, choć po ponownej aktywacji posiada ten sam zestaw atrybutów co przed deaktywacją. Niekatywny obiekt nie jest wyświetlany, nie jest brany pod uwagę przy kolizjach, nie jest również brany pod uwagę w przypadkach odwołania się do wszystkich obiektów danego typu (np. with(obj1) instance_destroy() znizczy wszystkie obiekty obj1 poza nieaktywnymi). Wynika z tego również, że nieaktywne obiekty nie mogą się same aktywować (bo nie są wyświetlane a ich zdarzenia nie są interpretowane).

Z tego względu warto w jednym obiekcie który będzie zawsze aktywny poświęcić kilka linijek na aktywację i deaktywację obiektów. Można to zrobić na kilka sposobów, z których ja zaprezentuję tylko najbardziej radykalny:

instance_deactivate_region(-10,-10,10+view_wview,10+room_height,false,true)
instance_activate_region(-10,-10,10+view_hview,10+room_height,true,true)

Pierwszy z nich deaktywuje WSZYSTKIE obiekty które znajdują się poza polem gry z włączeniem pierścienia o szerokości 10. Drugi z kolei aktywuje wszystkie obiekty WEWNĄTRZ tego obszaru. Upewnij się, że przed wykonaniem kodu odnoszącego się do obiektu sprawdzić, czy jest on aktywny - np. funkcją instance_number. Odwołanie się do obiektu nieaktywnego wygeneruje błąd.

Jeśli nie będziesz potrzebował wystąpienia któregoś obiektu, usuń go. Wystrzelony pocisk w większości gier po wyjściu poza jej obszar może zostać zniszczony bez zmian w mechanice rozgrywki. Po co więc wciąż liczyć jego pozycje i kolizje, kiedy nie jest to niezbędne?

Pewnym sposobem na przyspieszenie ładowania gry jest używanie jednego obiektu, zastępującego kilka. Przykładowo, znajdźki i bonusy mogą być w postaci jednego obiektu, z losowanym przy jego stworzeniu spritem. Używanie oddzielnego obiektu dla każdego typu bonusu to dłuższy czas ładowania gry. Analogicznie, zamiast kilku przycisków w menu możesz użyć jednego obiektu. Zamiast 4 różnych obiektów odpowiedzialnych za przyciski wstaw cztery identyczne obiekty, a w ich creation code przypisz im tylko inne sprity (sprite_index*).

Dobrą praktyką jest trzymanie w grze jednego obiektu, odpowiedzialnego za rysowanie. Przykładowo, jeśli chcesz dodać jakąś dynamiczną grafikę, ale nie potrzbeujesz do tego celu wszystkich zdarzeń obiektu (kolizja, pozycja itp.) używaj funkcji do rysowania (DRAW event) stworzonego wcześniej obiektu odpowiedzialnego za rysowanie. W ten sposób możesz nawet przy odrobinie samozaparcia stworzyć bardzo efektowne menu, z wykorzystaniem zaledwie jednego obiektu.

Views

Game Maker pozwala używać do 9 tzw. widoków (z ang. views). Pozwalają one pokazywać inną od domyślnej części ekranu, w dowolnym miejscu i w dowolnej skali (a także i rotacji). Jest to wspaniała opcja dla graczy tworzących gry multiplayer typu split-screen (z ang. podzielony ekran). W tym miejscu należy się jednak słowo uwagi na temat działania tej opcji.

Kolejność wykonywania zdarzeń jest w gruncie rzeczy taka sama jak kolejność eventów w oknie obiektu. Najpierw wykonywane są zdarzenia przy tworzeniu, poprzez kod z zdarzeń STEP, na samym końcu kiedy wszystko zostało policzone, sprawdzone i wykonane wykonują się zdarzenia DRAW.

Dla każdego widoku, room musi zostać przerysowany. Oznacza to, że nie używając widoków, kod z DRAWa wykonywany jest jeden raz w cyklu. Kiedy używasz dwóch widoków, drugi z nich musi być narysowany na nowo, co oznacza ponowne wykonanie zdarzeń z DRAWa. Każdy kolejny widok po raz kolejny wymaga przerysowania. Dlatego też korzystaj rozsądnie z opcji widoków.

Kolizje

W przypadku wieloobiektowych gier możesz zaoszczędzić trochę mocy obliczeniowej na poprawnym rozmieszczeniu zdarzeń kolizji. Zazwyczaj sądwie opcje: albo pocisk koliduje z graczem, albo gracz z pociskiem. Obiekty bez zdarzeń kolizji nie obciążają procesora tak bardzo, jak obiekty z tymi zdarzeniami. Warto więc stosować zdarzenia kolizji w tych obiektach, których jest mniej. Jeśli mamy na planszy jednego gracza i dziesięć lecących w jego kierunku pocisków, to przeniesienie zdarzeń kolizjii z obiektów pocisk do obiektu gracz zredukuje liczbe sprawdzań kolizji o 90%. Wniosek jest prosty - w przypadku zdarzeń kolizji dwóch obiektów dopilnuj, by były one zawarte w obiekcie o mniejszej ilości wystąpień. Możesz odwołać się w nim do drugiego obiektu (np. do pocisku) poprzez słowo kluczowe other.

Oszczędności w trybie 3d

Jeśli tworzysz grę akcji w 3d, priorytetem powinna być dla ciebie szybkość działania. Istnieje kilka metoda polepszenia wydajności grafiki 3d w Game Makerze.

Najważniejsze jest zastanowienie się, co w danej chwili musi a co nie musi być rysowane na ekranie. Każdy rysowany model to obliczenia jego pozycji, cieniowania, tekstur itd. Ograniczenie liczby rysowanych w jednym momencie modeli pozwoli przyspieszyć działanie gry. Przykładowo, możemy założyć, że obiekty w dali są niewidoczne i nie powinny być wyświetlane do momentu, kiedy będą odpowiednio blisko. Technika ta nazywa się clippingiem. W zdarzeniu DRAW takiego obiektu możesz sprawdzić funkcją point_distance odległość od kamery, i jeśli jest ona mniejsza niż pewna wartość wykonać kod tego zdarzenia (aby nie było nagłych przeskoków możesz również dodać mgłę - d3d_set_fog() - która zamaskuje braki obiektów w oddali. Do tego tło etapu powinno być identyczne co kolor mgły - szczegóły w helpie do GMa).

Analogicznie, nie potrzebujesz rysowania obiektów, które znajdują się za twoimi plecami. Zakładam, że artykuł ten kierowany jest do osób zaawansowanych, które z implementacją sprawdzania sobie poradzą. Potrzebne będzie osobne sprawdzanie dla kierunków 0-180 i 180-360 stopni, oraz dodatkowo dla 180 i 360 stopni. W pierwszych dwóch przedziałach należy za pomocą funkcji trygonometrycznych znaleźć współczynniki a i b równania y=ax+b prostej, przechodzącej przez punkt w którym jest gracz, i skierowanej prostopadle do jego linii widzenia. Potem w zależności od kierunku należy sprawdzić, czy wartość y odpowiadająca danemu x jest mniejsza/większa (zależnie od kierunku) niż wartość funkcji ax+b dla danego x. W przypadku stwierdzenia, że tak jest należy obiektu nie rysować. I w ten sposób można zaoszczędzić średnio 50% liczby koniecznych do wykonania operacji.

Jeśli zależy ci radykalnie na tempie działania, użyj dostępnego w GM cullingu. Co to właściwie jest? Już wyjaśniam. Normalnie, GM rysuje obie powierzchnie ściany czy podłogi, zatem widzisz ją niezależnie od której jesteś strony. Są jednak takie obiekty, które zawsze są widoczne od jednej strony. W takim wypadku rysowanie zawsze niewidocznej strony to marnotrawstwo. Po włączeniu cullingu (d3d_set_culling(true)) gra będzie rysowała tylko jedną stronę. Którą? To zależy od kolejności, w jakiej wpisałeś współrzędne (muszą być w kierunku wskazówek zegara). Po zaimplementowaniu tego możesz na własne oczy przekonać się, że gra nie rysuje tylnej powierzchni. Po raz kolejny daje to oszczędność rzędu 50%.

Sposobem na przyspieszenie gry jest stosowanie jak najmniejszej ilości vertexów na rzecz modelów. GM umożliwia wstawianie kilku prostych brył, z których można zrobić bardziej skomplikowane rzeczy w najbardziej naturalny i efektywny sposób. Jeśli więc bryłę, którą mozolnie konstruujesz z vertexów da się zastąpić czymś gotowym - nie wahaj się.

Funkcje, zdarzenia i inne

Pewne funkcje wymagają dużo operacji i przez to mogą spowalniać grę. Unikaj rozbudowanych instrukcji w zdarzeniach STEP i DRAW, które wykonywane są najczęściej. Większość rzeczy nie wymaga sprawdzania w każdym kroku - podczas tworzenia gry musisz się zastanowić, jak połączyć wydajność z efektownością. Oczywiście, kod w zdarzeniu STEP i DRAW wykonywany jest błyskawicznie, ale jednak czas na interpretację istnieje. Użycie iteracyjnych pętli czy pętli while w stepie może już bardziej widocznie wpłynąć na szybkość działania.

Jeśli pewien kod powtarza się wiele razy w grze, przerzuć jego działanie do skryptów i tylko wywołuj je w odpowiednich miejscach. Zyskasz porządek i mniejszy rozmiar kodu.

Miej również na uwadze, że niektóre funkcje są z natury wolniejsze niż inne, szczególnie te, które sprawdzają wszystke wystąpienia obiektu (np. akcja odbijania). Przykładem wolno działającej funkcji jest również draw_get_pixel() - używaj ich z rozwagą. Inne zagadnienia Używaj spritów najmniejszych jak to możliwe - nie pozostawiaj wokół nich przezroczystej obwódki (aby się jej automatycznie pozbyć, użyj fukncji CROP). Aniomwane sprity zajmują sporo miejsca w pamięci, ich rysowanie również zajmuje chwilę. Sposobem na polepszenie wydajności gry jest zmiana ich rozmiaru - jeśli zmniejszych dwukrotnie rozmiary sprite'a, to gra będzie zajmować w pamięci 4 razy mniej miejsca. Jeśli twoje tła pokrywają cały room, to wyłącz rysowanie koloru tła w opcjach rooma - poprawi to szybkość działania gry.

Game Maker płynnie i szybko przeskalowywuję grę do rozdzielczości ekranu przy grze na pełnym ekranie. Gdy twoja gra nie uruchamia się w oknie dopilnuj, by rozmiar widocznej części gry nie był większy niż rozmiar ekranu. Większość kart graficznych potrafi wydajnie skalować obrazki i rozciągać je, lecz słabo sobie radzi z zmniejszaniem obrazków.

Gdziekolwiek jest to możliwe, staraj się wyłączyć wyświetlanie kursora (window_set_cursor(cr_none) w kodzie wyłączy jego wyświetlanie). Przyspieszysz w ten sposób nieco wyświetlanie grafiki.

Obsługa dużych plików dźwiękowych w GM pozostawia wiele do życzenia. Zajmują one bardzo dużo miejsca, a przy tym źle się kompresują. Warto sprawdzić, czy można zredukować ich jakość w programie dźwiękowym.

Użycie funkcji globalnych w przypadku wartości, do których się często odwołujemy jest szybsze, aniżeli ustawianie i pobieranie wartości lokalnych. Pewne parametry, takie jak pozycja bohatera, jego siła opadania czy inne, niezbędne i często sprawdzanie lepiej ustawiaćw wartościach globalnych.

I przede wszystkim - przed wydaniem gry sprawdź jej działanie na kilku starszych komputerach :)

Źródło

informacje na temat funkcji, użytych w artykule: instance_destroy(), draw_get_pixel(), instance_deactivate_region(), instance_activate_region(), d3d_set_fog(), d3d_set_culling()