Leveldesign w Skullstone


#1

Cześć. Poniżej przedstawiam tekst, który docelowo chcę opublikować na IndieDB oraz na moim profilu LinkedIN (oczywiście po angielsku). Jako, że nie mam wprawy w pisaniu czegoś takiego to proszę Was o ocenę.


Jednym z końcowych procesów tworzenia gry jest leveldesign. Polega on na utworzeniu map/terenów oraz wypełnieniu ich obiektami, postaciami NPC, przeszkodami, przeciwnikami, i tak dalej. Większość takich obiektów spełnia jedynie rolę dekoracyjną lub stanowi prostą przeszkodę, nie jest niczym innym jak obiektem graficznym połączonym z colliderem lub specjalnie wydzielonym obszarem na mapie. Jednak pozostałe wymagają już nieco więcej uwagi ze strony projektanta. Przeciwnicy muszą być wyposażeni w AI, NPC muszą mieć podpięte systemy dialogów a obiekty interaktywne muszą być oskryptowane. Takich rzeczy nie hardkodujemy, sterowanie nimi musi być prostsze niż pisanie kodu gry, możliwe dla tych, którzy często do takiego kodu nie mają dostępu.

Proste systemy dialogowe pozawalają na układanie tekstów w ‘drzewka’ z możliwością wywołania przygotowanej wcześniej funkcji w samej grze - na przykład odblokowania jakiejś funkcjonalności, wręczenia nagrody (przedmiotu), przyznania punktów doświadczenia lub aktywacji subquesta.
AI to przeważnie mniej lub bardziej skomplikowane algorytmy analizujące sytuację na planszy gry i podejmujące decyzję o kolejnej akcji do wykonania. Bywa, że są konfigurowane zaledwie kilkoma parametrami takimi jak poziom agresji, przynależność do grupy czy… możliwość używania cheatów (tak, AI często nie gra tak jak zwykły gracz tylko idzie na skróty). Zaawansowane systemy dają możliwość precyzyjnego dostosowania zachowania do poszczególnych sytuacji, wybraniu parametrów świata gry, na podstawie których AI zaplanuje swoje dalsze postępowanie. Świetnym przykładem takiego zaawansowanego rozwiązania jest nasz polski system Grail.

Sam proces tworzenia mapy i układania obiektów może być rozwiązany na wiele sposobów, jeśli korzystamy z silnika Unity to najprostszym jest użycie jego wbudowanego edytora scen, rozmieszczamy tam w przestrzeni obiekty utworzone z przygotowanych wcześniej prefabów. Często jednak programiści tworzą własny edytor, który może znacznie przyspieszyć pracę designera. Tak właśnie postąpiliśmy w Skullstone. Specyficzna budowa mapy podziemi pozwoliła na bardzo szybkie jej tworzenie - mapa to siatka, oznaczamy kratki, które są korytarzem, ściany, podłoga oraz sufit zostają automatycznie wygenerowane. Dodatkowo każda kratka może zawierać obiekt (interaktywny lub nie) na podłodze oraz na każdej ze swoich krawędzi. Klikamy na kratkę oraz wybieramy jeden z gotowych elementów z listy, na przykład dźwignię, przycisk, drzwi, pajęczynę… Struktura takiej mapy też jest prosta, sprowadza się ona do zapisania identyfikatorów klas obiektów oraz ich położenia, czyli x,y i kierunek. To, co widzimy na ekranie jest dynamicznie tworzone za podstawie takiego zapisu.


Gracz w trakcie rozgrywki może położyć przedmiot na podłodze, wykorzystaliśmy ten fakt podczas tworzenia poziomów. Nie musimy dublować istniejącej w grze mechaniki, jednym z elementów tworzenia poziomu jest więc poruszanie się po mapie w ‘god mode’ i rozmieszczanie przedmiotów, które potem znajdzie gracz. Rozmieszczanie przeciwników jest jeszcze prostsze - wybieramy rodzaj przeciwnika z listy i klikamy odpowiedni przycisk w naszym edytorze. Mob pojawia się na kratce bezpośrednio przed postacią gracza. Po zaznaczeniu go na liście wszystkich mobów z danego poziomu możemy nadać mu dodatkowe parametry, takie jak grupa do której przynależy czy początkowe zachowanie (spanie, pilnowanie).

Ok, ale co zrobić, żeby to wszystko zaczęło ‘żyć’, jak stworzyć logikę gry? W jaki sposób powiązać np dziurkę na klucz z drzwiami? Jak sprawić, żeby wciśnięcie przełącznika zamykało zapadnię? Najbardziej oczywistym wydaje się oskryptowanie zachowania każdego z obiektów. W Unity dodali byśmy stworzony przez nas komponent który wykryje zmianę pozycji przełącznika i wykona zadaną akcję na powiązanym obiekcie ze sceny. Na podobnej zasadzie działa oskryptowanie obiektów za pomocą języków Lua lub Python, interpretery tych języków musimy dołączyć do silnika gry i odpowiednio z nim powiązać.
W Skullstone zrobiliśmy to dużo prościej. Każdy z obiektów ma dwa stany logiczne: otwarty/zamknięty, włączony/wyłączony, żywy/martwy itd. Potraktowaliśmy więc każdy z nich jako maszynę dwustanową. Najbardziej oczywistą rzeczą będzie więc przeniesienie stanu z jednego obiektu na drugi. Jak wspomnieliśmy wcześniej, każdy z obiektów jest zapisany jako klasa+x+y+kierunek a to umożliwia nam bardzo łatwe zaadresowanie takiego obiektu. Aby móc sterować zapadnią za pomocą przycisku jedyne co potrzebujemy to przenieść stan z jednego obiektu na drugi. Opracowaliśmy więc nasz własny język, który to umożliwia. Podstawowa instrukcja składa się z adresu obiektu, na którym ma zostać wykonana, rodzaju czynności (przeniesienie stanu, blokada, timer itp) oraz parametrów. W przypadku przeniesienia stanu jednym z parametrów będzie adres obiektu docelowego oraz wartość mówiąca, czy stan powinien być zanegowany czy nie.

Z czasem okazało się, że oprócz operowania na stanach, skrypt może być użyty do ustawienia właściwości konkretnych obiektów. W ten sposób podłączamy teksty do tabliczek na ścianach i konfigurujemy ołtarze (czym one są zobaczycie w grze).
A co w sytuacji, gdy chcemy zdefiniować bardziej złożone zachowanie? Kiedy musimy przechować jakieś dane i wywołać pewną sekwencję zdarzeń? Potrzebne są komórki pamięci. Mamy je. Każdy z obiektów dwustanowych może być taką komórką pamięci przechowującą wartość 0 lub 1. Gracz nie musi ich widzieć, ukrywamy je w jakimś niedostępnym dla gracza miejscu. Te przekreślone kratki to właśnie nasz obiekt ‘pamięci’. Nie ma żadnej specjalnej logiki, nie jest interaktywny, nie jest nawet widoczny! Po prostu pamięta stan i można go zaadresować. Odpowiednia sekwencja instrukcji przenoszących stan oraz warunkowo blokujących zmianę stanu (zmiana stanu jest zablokowana gdy na innym obiekcie występuje dany stan) dała nam w efekcie drzwi, które możemy otworzyć i zamknąć zaledwie kilka razy po czym zostaną trwale zablokowane. Fajna pułapka, prawda? Zwłaszcza jeśli za tymi drzwiami czeka grupa krwiożerczych potworów.

Mam nadzieję że nasze nietypowe rozwiązanie się podoba i Ze może zainspiruje ono kogoś do stworzenia czegoś równie ciekawego. Chętnie o tym poczytamy.