Project Spark - kurs tworzenia gier cześć 5, przeciwnicy, silnik, szablony oraz sektory
19.03.2014 | aktual.: 20.03.2014 09:33
W poprzednich lekcjach pokazałem jak programować skrypty, tworzyć poziomy, animować elementy oraz budować złożone mechanizmy np. działającą katapultę. Jeżeli jednak ktoś podążał tym kursem to pewnie zauważył że tworzone przez nas gry były tworzone "na jeden raz" i problemem byłaby ich modyfikacja oraz rozbudowa. Jeżeli oprogramowalibyśmy jakiegoś przeciwnika w jednym miejscu gry a później chcielibyśmy użyć ten model w innym to robiliśmy jego kopię. Kopię którą trzeba byłoby późnej osobno modyfikować w kilku miejscach. Szybko stracilibyśmy kontrolę nad naszym kodem oraz możliwość jego łatwej modyfikacji. Czas zbudować własny silnik gry oraz poznać podstawowe zasady optymalizacji.
Jeżeli ktoś trafił na ten artykuł nie czytając wcześniejszych części to zachęcam do przeczytania wszystkich po kolei ponieważ są one wzajemnie ze sobą powiązane. Każda lekcja kursu bazuje na wiedzy wyniesionej z poprzednich lekcji.
- Programowanie skryptów
- Modelowanie otoczenia
- Ścieżki ruchu oraz praca kamery
- Obiekty złożone, mechanika i fizyka
Typowa gra zbudowana jest na bazie kilku zasad. Mamy stworzony świat lub generujemy go dynamicznie. Jest kilka rodzajów wrogów których umieszczamy w poszczególnych lokacjach. Silnik musi dbać o optymalizację by gra działała szybko. Nigdy nie tworzymy naszego świata w całości ale staramy się go podzielić na sektory tak by w danej chwili nasz silnik w pełni obsługiwał tylko sektor w którym znajduje się gracz. Nie ma sensu umieszczać 100 goblinów chodzących 2 kilometry od gracza których ten nigdy nie zobaczy bo procesor komputera będzie się musiał nimi zajmować. W tej lekcji stworzymy prosty poziom który będzie zawierał dwa przykładowe sektory. Każdy z nich będzie posiadał drzwi których otwarcie spowoduje utworzenie w środku przeciwników powiązanych z tym sektorem. W naszej grze będą tworzone tylko te obiekty z którymi gracz może wejść w interakcję. Dodatkowo przeciwnicy będą stworzeni na bazie szablonów by kod źródłowy był łatwy w utrzymaniu i rozbudowie. Sam poziom udostępniłem z konta użytkownika którego utworzyłem specjalnie dla tego kursu. Użytkownik nazywa się SatiricalCrab94 a poziom to Kurs tworzenia gier - lekcja 5
Wzorce obiektów
Zacznijmy od stworzenia naszego wzorcowego przeciwnika. Dla ułatwienia wybierzemy gotowy model Wojownika. Można go znaleźć w sekcji z obiektami złożonymi pod nazwą "Enemy - Bandit Fighter". Umieszczamy go na naszej planszy. Jeżeli otworzymy jego skrypt mózgu to zobaczymy że ma on aż 8 stron. Poszczególnie strony odpowiadają za kolejne stany jego umysłu takie jak "przechadzania się ", "podbiegnięcie do wroga", "atak", "odskok" itp. Logika jest dosyć rozbudowana a w miarę jak będziemy ja modyfikować całość będzie jeszcze bardziej złożona. Dodatkowo możemy ustalić naszemu wojownikowi interesujący nas wygląd, uzbrojenie, pancerz, kolory itp. W typowej grze możemy mieć kilka rodzajów przeciwników którzy będą współdzielili pewna logikę ale różnili się wyglądem. Nie ma jednak sensu utrzymywać kilku bardzo podobnych skryptów dla poszczególnych rodzajów wojowników. Dlatego nasz skrypt za chwilę wyciągniemy z obiektu na zewnątrz by móc stworzyć trzy typy wojowników współdzielących logikę ale różniących się wyglądem oraz wyposażeniem.
Wyciągnięcie skryptu na zewnątrz jest odpowiednikiem stworzenia tzw. "klasy" w obiektowych językach programowania. Chodzi o to by zamiast kilku kopii kodu w poszczególnych miejscach użycia mieć skrypt w jednym miejscu jako tzw. bazę którą inne obiekty mogą rozbudować lub zmieniać. By to zrobić tworzymy nowy klocek logiki i kopiujemy do niego całą zawartość skryptu mózgu naszego wojownika. W trakcie kopiowania zwracamy uwagę na zmienną "my weapon". Domyślny skrypt wojownika posiada zmienną do której przypisuje broń która ma zostać użyta w pozostałych miejscach skryptu. To bardzo ułatwi nam pracę ponieważ do tej zmiennej możemy przypisać dowolną broń dla poszczególnych typów przeciwników. Gdy skopiujemy kod skryptu do kostki logicznej to kasujemy całą zawartość skryptu mózgu z obiektu wojownika. Następnie dodajemy tam jedną linijkę kodu która spowoduje załadowanie skryptu mózgu z kostki logicznej w momencie utworzenia obiektu. Dzięki temu mamy cały kod w jednym miejscu niezależnie ile rodzajów wojowników będzie występowało w naszej grze. Na obrazku jest jeszcze druga linia z przypisaniem broni co wyjaśniłem poniżej
Ponieważ chcemy mieć kilka rodzajów wojowników z różnym uzbrojeniem i wyglądem to kopiujemy kilka razy naszego wojownika i dostosowujemy jego wygląd. Jednak nie ustalamy żadnego uzbrojenia w character studio. Nasze uzbrojenie chcemy przypisać do zmiennej "my weapon". Wycinamy linijkę przypisania broni z naszej kostki logicznej i umieszczamy ją bezpośrednio w obiekcie każdego wojownika wybierając różną broń. Dzięki temu nasza kostka logiczna zawiera kod operujący na zmiennej "my weapon" a my w obiektach pochodnych jedynie ustalamy wartość tej zmiennej. W skrypcie bazowym wojownika upewniamy się też by zamiast walczyć wręcz walczył on przypisaną bronią. W tym celu usuwami linijkę z blokiem 'unequip' ze strony ataku.
Fabryka obiektów
Rozwiązaliśmy problem skryptu ale nadal każdy z naszych typów wojowników ma ustalony przez nas unikalny wygląd, kolory, wielkość, zasady fizyki, uzbrojenie. Nie możemy go skopiować w kilka miejsc planszy ponieważ musielibyśmy osobno zarządzać każdą kopią przy późniejszych modyfikacjach. Czas poznać popularny wzorzec projektowy o nazwie "Object Builder" nazywany także Fabryką. Wzorzec ten polega na tym że ustalamy jeden lub więcej wzorcowych obiektów nazywanych szablonami (template) a następnie w poszczególnych miejscach planszy tworzymy ich kopie. Zacznijmy od ustawienia każdemu naszemu wojownikowi właściwości "template" na "true" poprzez edycję jego właściwości w sekcji "Brain". Obiekty takie nie będą widoczne w grze a jedynie będą stanowiły wzorzec na bazie którego tworzyć będziemy poszczególnych przeciwników. Rolę naszej fabryki będą spełniały klocki logiczne które umieścimy w interesujących nas miejscach. Każdy taki klocek zna swoją pozycję w której się znajduje więc może w tym miejscu stworzyć dowolne inne obiekty co użyjemy do tworzenia przeciwników. Można stworzyć albo osobne klocki dla każdego typu wojownika albo umieścić w nim jakąś logikę która utworzy losowo jeden z modeli. W naszym przykładzie skorzystamy z tej drugiej opcji. Obiekty fabryki możemy także tworzyć za pomocą innej fabryki jeżeli ich logika jest na złożona. Tak by nie kopiować kodu w kilka miejsc. W naszym przykładzie jednak tego nie zrobimy by całość była czytelna dla osób początkujących. Skrypt naszej fabryki wygląda następująco.
W momencie gdy obiekt dostanie zasilanie to losuje on liczbę z zakresu od 1 do 3 która określi ilość przeciwników (plus jeden bo zliczamy w naszym kodzie od zera), Wartość ta zostaje przypisana do zmiennej 'enemy team size'. Tworzymy drugą zmienną określającą ilu przeciwników już utworzyliśmy. Zmienne te inicjujemy jeden raz co kontrolujemy za pomocą zmiennej 'initialized'. Nie użyłem tutaj reguły 'once' ponieważ chcę mieć możliwość resetowania stanu fabryki tak by mogła ona ponownie zadziałać tworząc nowe obiekty np. po resecie gry. W dalszej części naszego skryptu znajduje się porównanie które kontroluje ilość utworzonych obiektów. W trakcie każdego przejścia skryptu przypisujemy pozycję kostki logicznej do zmiennej tymczasowej 'Spawn position'. Następnie modyfikujemy wartość X oraz Z o plus/minus 1 metr tak by poszczególni wrogowie pojawili się w pobliżu klocka logicznego. Warto zwrócić uwagę że oś Y w edytorze Project Spark skierowana jest pionowo więc używamy X oraz Z by rozrzucić tworzone obiekty w poziomie. Po ustaleniu zmiennej pozycji wywołujemy regułę tworzenia obiektu wojownika.
Druga strona naszego skryptu jest jeszcze prostsza. Losuje ona liczbę od 1 do 3 i na tej podstawie tworzy losowy typ przeciwnika. Obiekty tworzone są w miejscu określonym przez utworzoną wcześniej zmienną 'Spawn position' oraz ustalamy że każdy z nich zwrócony jest w kierunku gracza. Dodatkowo każdy z obiektów zdecydowałem się dodać do zbioru 'created enemies' by kontrolować utworzone obiekty
Sektory
Mamy już szablony naszych przeciwników oraz przykładowy klocek z fabryką która tworzy obiekty. To wystarczy by dodać do naszego silnika obsługę sektorów. Tak jak napisałem wcześniej ważne jest by unikać tworzenia obiektów których gracz nie widzi. Project Spark liczy w każdej klatce animacji każdy aktualnie aktywny skrypt w naszej grze. Musimy więc kontrolować ilość obiektów aktywnych by nie obciążać niepotrzebnie procesora. Jeżeli stworzymy labirynt z setką wrogów to całość zacznie zwalniać ponieważ silnik gry będzie liczył zachowanie się 100 przeciwników choć widoczny będzie tylko kilku w pobliżu naszego bohatera. Musimy więc podzielić nasz poziom na poszczególne pomieszczenia lub sektory widoczności. W każdym takim sektorze umieszczamy nasze kostki logiczne fabryki przeciwników w tych miejscach które nas interesują. Następnie programujemy zasady która aktywuje poszczególne fabryki. W naszym przypadku zdarzeniem takim będzie otwarcie drzwi do danego pomieszczenia ale można oczywiście wymyślić wiele innych zdarzeń. Popularnym jest umieszczenie niewidocznej kostki logicznej która się aktywuje jeżeli gracz znajdzie się w jej pobliżu (reguła Detect).
A co z wrogami których stworzymy ale gracz przed nimi ucieknie? Te warto zniszczyć żeby nie śmieciły nam naszej planszy i nie spowalniały gry. By to osiągnąć dodałem do naszej kostki logicznej zbiór z kolekcją utworzonych obiektów. Taki zbiór umożliwi nam łatwe zniszczenie obiektów gdy nie będą one już potrzebne. Zniszczenie obiektów jest przydatne w kilku scenariuszach. Pierwszym i najbardziej oczywistym jest restart gry. W przypadku restartu niszczymy wszystkie obiekty utworzone oraz ponownie ustawiamy każdą fabrykę jako niezainicjowaną. Dzięki temu gracz może przejść po raz drugi ten sam poziom. Innym przykładem jest "hibernacja" wrogów gdy gracz przechodzi z na niższy poziom lochów. Nie ma sensu by obiekty na wyższym poziomie dalej obciążały naszą grę. Lepiej pobrać kolekcję tych obiektów, zapamiętać ich typ oraz pozycję w jakiejś kolekcji a same obiekty zniszczyć. Dzięki temu pozbędziemy się z poziomy dużej ilości aktywnych skryptów a ciągle będziemy mogli te obiekty odtworzyć za pomocą odpowiednio napisanej fabryki jeżeli będzie taka konieczność (gracz wróci na wcześniejszy poziom labiryntu). W udostępnionym przykładzie umieściłem dodatkowo przełącznik którego celem jest zresetowanie poziomu do stanu podstawowego. Przechodzi on po kolekcji obiektów fabryk i przywraca je do stanu początkowego oraz zamyka otwarte drzwi.
Dynamiczne lochy, zmienne pory dnia i roku
Mam nadzieję że ta lekcja była ciekawa i udało mi się pokazać jak zbudować podstawy naszego własnego silnika. Pozwala nam to lepiej zarządzać zmianami w kodzie, zaprojektować znacznie większe poziomy oraz lepiej zarządzać mocą obliczeniową komputera. W tej lekcji zajmowaliśmy się jedynie tworzeniem przeciwników ale na podobnej zasadzie możemy tworzyć także inne obiekty. Możemy nawet dynamicznie tworzyć nasze własne labirynty co autorzy gry zademonstrowali w jednym ze swoich kursów kilka miesięcy temu. Łącząc dynamiczne tworzenie przeciwników z dynamicznym tworzeniem labiryntu można zbudować całkiem sensową grę na podobieństwo Diablo 1. Warto zobaczyć to wideo, prezentacja rozpoczyna się ok 24 minuty filmu. Warto też zobaczyć przykład z rosnącymi drzewami z uwzględnieniem pory roku - rosnące drzewa
Od dziś Project Spark jest dostępny dla każdego za darmo
Od 18 marca 2014 nie są wymagane klucze beta. Każdy może ściągnąć aplikację i tworzyć własne gry bez potrzeby wcześniejszej rejestracji. Zlikwidowano także abonament Spark Time który wcześniej ograniczał możliwość grania w gry używające DLC. Teraz każdy może grać we wszystko za darmo nawet jeżeli nie odblokował samemu zawartości DLC.
Project Spark - Sklep Windows 8.1
Na potrzeby tego kursu założyłem dodatkowe konto w grze gdzie będę publikował kod dla poszczególnych lekcji. Aktualnie są tam tylko najnowsze lekcje. Zapraszam. SatiricalCrab94. Poziom opisany w tym kursie udostępniony jest publicznie pod nazwą Kurs tworzenia gier - lekcja 5. Postaram się udostępniać każdą następną lekcję jako osobny projekt