Anti-Botic, czyli jak napisać grę w 48 godzin

PL
Data dodania: 2011-10-07, Autor: tomkh, Dodał: Karol, Wyświetleń: 787

Motywacja

Masz pomysł na grę? Jeśli tak, to założę się, że jesteś przekonany o jego świetności i oryginalności. Z pewnością godzinami grałeś w inne gry, być może zastanawiałeś się, co im brakuje, i jak Ty zrobiłbyś to lepiej, być może wyodrębniłeś z wielu produktów to co najlepsze i chcesz to wszystko wykorzystać we własnym super grywalnym produkcie, być może masz swoje własne oryginalne pomysły na wybrane elementy gry, zupełnie nowe, takie, których jeszcze nigdzie nie ma. Jeśli tak, to gratuluje, jesteś na dobrej drodze, ale pamiętaj o jednym: Twoje pomysły, mogą być z różnych przyczyn, niemożliwe do zrealizowania, szczególnie jeśli na napisanie gry masz 48 godzin! Zanim zaczniesz produkować kolejne przerośnięte komercyjne gry z dużą ilością grafiki i nudną fabułą, warto zrobić coś prostszego, grywalnego, nabrać trochę doświadczenia. Organizowane są konkursy internetowe na napisanie gry, w krótkim czasie, przykładowo przez serwis http://ludumdare.com, w których uczestniczą programiści z całego świata. Wydaje się, że dobrym wyborem, oprócz konkursów internetowych, może być też udział w konkursach organizowanych coraz częściej na dużych scenowych party (takich jak Assembly, Breakpoint). Niestety nie miałem przyjemności wzięcia udziału na takim. Anti-Botic jest grą, napisaną przeze mnie na konkurs organizowany przez ludzi związanych z http://ludumdare.com 17-18 kwietnia 2004. Jako, że właściwie niespecjalnie lubię rywalizację, a jeszcze bardziej ograniczanie mojej swobody twórczej (ustalony termin, temat gry i krótki czas realizacji) musiałem się na początku przełamać, aby wystartować, ale teraz myślę, że dużo zyskałem. W dalszej części tego artykułu opiszę ogólną ideę konkursów Ludum Dare oraz swoje przeżycia i szczegółowy przebieg 4-tej edycji, w której brałem udział.

Co by tu zrobić?

Uczestnicy zaczynają od głosowania na temat gry. Temat jest przeważnie tylko krótkim, ogólnym sformułowaniem, dotyczącym tego co powinno się znaleźć w grze. W 4-tej edycji konkursu, wygrał temat: "Infekcja, dosłownie, lub po prostu coś, co się rozprzestrzenia". Temat został podany do wiadomości w sobotę o 3 nad ranem (czasu lokalnego) i tym samym rozpoczęła się rywalizacja. Z powodu mojej rozterki, czy wogóle brać udział, dopiero w połowie pierwszego dnia, zacząłem zastanawiać się co by tu zrobić. Pierwsze pomysły były oczywiście irracjonalne (chociaż nigdy nie wiadomo). Chciałem napisać rysowany "głuchy telefon" na wielu graczy (multi-player). Każda z osób miała rysować coś na kartce i pokazywać drugiej przez kilka sekund swój obrazek, kolejna osoba na podstawie tego co zapamiętała przez ten krótki czas ekspozycji, miała to narysować następnej, i tak dalej. Byłem oczywiście zafascynowany tym pomysłem, ale z czasem, stwierdziłem, że to jednak nie jest to. Przypomniało mi się wtedy, że kiedyś napisałem już coś w temacie infekcji, była to symulacja życia komórek, ale nie w stylu klasycznej "gry w życie", gdzie matematyczna metafora w stosunku do rzeczywistych komórek jest bardzo uproszczona (komórki to pionki na szachownicy), tylko bardziej realistyczna: komórka była punktem i poruszała się swobodnie, rozmnażając się i zjadając inne. Kontrolę populacji zapewniała zmienna określająca całkowitą energię układu (analogiczną do energii słonecznej czy ilości pożywienia), którą ustawiałem w zależności od tego, czy chciałem uzyskać zwiększenie, utrzymanie czy zmniejszenie ilości komórek. Ponieważ było mało czasu, dobrze było oprzeć się o coś sprawdzonego, tym bardziej, że wymyśliłem i sprawdziłem to kiedyś osobiście. Wystarczyło teraz zaimplementować od nowa podobny system komórek, z kilkoma rodzajami: czerwone krwinki, krwinki zawirusowane i antybiotyk i wrzucić do tak spreparowanego krwiobiegu jakiś pojazd, którym mógłby sterować gracz (tutaj mała uwaga: antybiotyk to substancja płynna niszcząca proces tworzenia się ściany komórkowej, a nie bakteria, komórka, czy jakikolwiek inny żywy organizm, jednak dla dobra gry pozwoliłem sobie na małe ubarwienie rzeczywistości i w mojej grze cząstki antybiotyku nie dosyć, że żyją to jeszcze odrobinę myślą :-). Pojazd gracza miał z początku zarówno strzalać do zawirusowanych komórek jak i wypuszczać antybiotyk, ale potem uznałem strzelanie za zbyt banalne i zostałem przy samym antybiotyku.

Do dzieła!

Pomysł wydawał się niezły, dlatego podniecony, zacząłem pisać pierwszy kod. Podjąłem decyzję, że będę używał swoich starych sprawdzonych sposobów kodowania, czyli strukturalne C z drobnymi elementami C++, prosty wrapper do Direct Draw (grafika 2d), programowy rendering do 32-bitowego bufora ramki, proceduralne generowanie zarówno planszy jak i wszystkich obiektów w grze. Stworzyłem projekt w Visual C++ 6.0, szybko zmontowałem jakąś kompilującą się aplikację testową (myślę, że bardzo ważne jest mieć przez cały czas tworzenia kompilujący się i wyświetlający coś na ekranie kod, wtedy sam proces jest przyjemny i cały czas możemy dokładnie obserwować postępy, czyli mamy stałe sprzężenie zwrotne, szczególne przydatne jeśli wprowadzamy mnóstwo niewielkich poprawek). Aplikacja wyświetlała na środku ekranu duży drgający napis Anti-Botic, i wydawała dźwięk bałałajki po naciśniećiu przycisku myszy. Stworzyłem kilka podstawowych plików: main.cpp (główna pętla gry), level.cpp (proceduralne generowanie świata gry), cells.cpp (sztuczna inteligencja komórek), blob.cpp (generowania obiektów i funkcje do rysowania), wszystkie dane statycznie osadzone w pamięci, jeden plik nagłówkowy shared.h, aby było prościej i wygodniej. Zacząłem od wygenerowania wyglądu komórek (stablicowany bloby, o nieco urozmaiconych funkcjach energii bloba od odległości do jego środka) i zaimplementowaniu funkcji "update" systemu poruszania i symulacji procesów życiowych komórek (bazującemu na wcześniejszym doświadczeniu). Wstępnie ustaliłem, że symulacja życia komórek będzie odbywać się ze zmiennym krokiem czasowym, jednak potem zdecydowałem, że tak samo jak poruszanie się pojazdu, cała symulacja będzie ze stałym krokiem (20 klatek na sekundę), z interpolacją linową pozycji pojazdu podczas rysowania, jedynie obracanie się pojazdu, miało odbywać się z taką samą prędkością jak rendering. Zaimplementowanie dobrej symulacji komórek, szczególnie różnego rodzaju, wymagało sporo kombinowania, eksperymentowania, testowania na bieżąco rezultatu modyfikacji (jak już mówiłem, środowisko pracy umożliwiające szybkie sprzężenie zwrotne to podstawa). Początkowo myślałem, aby ruch komórek sterowany był nieco szumem Perlina, jednak, po uzyskaniu niezbyt zadowalających efektów, zdecydowałem się na prosty ruch Browna dla czerwonych krwinek (losowo zmieniający się kierunek ruchu w czasie) i ruch grawitacyjny dla komórek-drapieżników (komórki zawirusowane, antybiotyki), przy zachowaniu pełnej deterministyczności ruchu (możliwość późniejszego zrobienia powtórek, chodzi po prostu o użycie własnej funkcji "rand" z osobnym, prywatnym ziarnem i wywoływaniu jej tylko dla deterministycznych zdarzeń). W między czasie stworzyłem proceduralną teksturkę na bazie szumu Perlina, z dziedziną modyfikowaną też szumem Perlina o niższej częstotliwości (ciekawy efekt turbulencji) i mogłem sobie urozmaicać proces dopracowywania symulacji komórek wciskając klawisz spacji i delektując się piękną proceduralną czerwoną teksturką (a co, programowanie musi być przyjemne :)).

Cieszący oko widok proceduralnej teksturki, motywujący do dalszej pracy.

Zaimplementowałem funkcję zbliżeń (zoom-in/zoom-out), okazała się średnio przydatna i nastręczała trochę problemów, dlatego szybko z niej zrezygnowałem. Przyszedł czas na wygenerowanie planszy. Znowu skorzystałem z własnych, sprawdzonych metod i wygenerowałem "jaskinię", a mianowicie, plansza była całkowicie losowa, generatorami planszy było 96 losowo rozmieszczonych punktów (znów trochę poeksperymentowałem zanim do tego doszedłem), do których przypisane były atrybuty 1=tkanka wewnętrzna (przypominająca mięso) / 0=środek naczynia (w którym można się poruszać) na podstawie kształtu wyciętego w środku dysku. Ostateczna tekstura naczynia to wcześniej wygenerowana czerwona teksturka (mięsko) przemnożona przez współczynnik przeźroczystości ustalony na podstawie interpolacji liniowej w zbiorze nieuporządkowanych punktów (bardzo prosta i brutalna implementacja). Dodatkowo zmodyfikowałem funkcję turbulencji "mięska" funkcją odgległości od najbliższego punktu ze zbioru, co dało dodatkowy, bardzo pożądany efekt organiczności. Jeszcze wizualnie dobrałem odpowiednie początkowe ziarno generatora losowego, aby plansza wyglądał na prawdę sensownie (zostawiłem potem możliwość modyfikowania początkowego ziarna generatora -> także gra posiada dużo więcej, aczkolwiek dosyć podobnych do siebie, plansz). Zadowolony z rezultatu zacząłem dopracowywać trochę wygląd komórek, zintegrowałem wszystko, napisałem proste kolizje dla komórek (odbijanie się od ścianek) i algorytm generowania początkowych pozycji komórek. Plansza była większa niż ekran, potrzebny był scrolling, podczepiłem go pod ruch myszki. Po kilku poprawkach byłem gotowy pokazać innym uczestnikom konkursy pierwszego screenshota.

Pierwszy screnshot z gry.

Na koniec jeszcze stworzyłem zieloną, flegmowatą teksturkę "infekcji" wirusowej (prosty szum fraktalny), która nakładana była na naczynie. Wyglądało ładnie, trzeba było jeszcze trochę doszlifować, dobrać idealne kolorki i zanim się nie obejrzałem była 6 nad ranem :) Przyszedł najwyższy czas na sen. Przed zaśnieciem myślałem sobie, o tym, jak to następny dzień zapowiada się ciekawie i ile pracy mnie jeszcze czeka.

Dzień drugi

Wstałem praktycznie w porze obiadowej, i od razu zabrałem się do pracy, no może nie od razu. Spotkałem się jeszcze z dziewczyną, która wpadła do mnie, aby przyjrzeć się jak szybko stukam po klawiaturze :) Była gdzieś 17-18 po południu, do zrobienia był jeszcze pojazd gracza. Stworzyłem plik player.cpp, wygenerowałem szybko oświetloną kulkę (z drobnym nadpróbkowaniem, aby ładniej wyglądała) z cylindrycznymi lufkami, taki szary nano-robocik, który na początku miał strzalać laserem i wypuszczać antybiotyki. Cała grafika (plansza, komórki, pojazd) miała z założenia generować się za każdym razem od początku, dzięki temu cała gra bardzo mało zajmowała (przed wygenerowaniem planszy) i mogłem w wygodny sposób wszystko poprawiać (kolorki, kształt obiektów itp..). Od razu generowałem klatki pojazdu ze wszystkimi możliwymi obrotami i animacjami, żeby nie bawić się specjalnie w procedurki do rysowania, oraz żeby po obrocie nie było efektów aliasingu (wszystkie funkcje rysujące to software, a nie chciało mi się implementować filtrowania tekstur). Moja dziewczyna stwierdziła: może lepiej zrobić tabletkę zamiast jakiegoś tam robocika, poruszająca tabletka była by śmieszniejsza. A ja na to: Tak! Oczywiście też tak myślałem, ale nie byłem pewien na co się zdecydować, no to robimy. Kształt super-elipsy (normalny niejawny wzór na elipsę ma potęgi kwadratowe, super-elipsa to dowolne potęgi, czyli |x/w|^n + |y/h|^n = r^n) doskonale przypominał podłużną tabletkę antybiotyku, trzeba było ją jeszcze odpowiednio pokolorować (niebiesko-białe połówki) i przełamać na pół (animacja otwierania), parę przekształceń, eksperymentowania, poprawiania i gotowe. Podczas tworzenie, oczywiście podczepiłem sobie wszystko pod mysz (obrót i animację otwierania tabletki) i przez parę minut musiałem się zwyczajnie pobawić efektem swojej pracy, obracając, otwierając i zamykając tabletkę i ciesząć się, że fajnie się rusza i ładnie wygląda.

Po lewej: otwarta tabletka wypuszczająca antybiotyk. Po prawej: zamknięta tabletka i zielona flegma infekcji.

Teraz już tylko kod poruszania gracza, przerobiłem całą symulację na stały krok czasowy, interpolacja pozycji gracza (trochę szwankowała, ale w końcu ładnie zadziałało). Obracanie tabletką, otwierania, wypuszczanie antybiotyku, ograniczona liczba antybiotyku w tabletce i już się dało grać! Musiałem dodać jeszcze kod usuwania zielonej flegmy infekcji z planszy (flegma była wrysowywana w teksturę planszy, przy usuwaniu trzeba było odświeżyc zapamiętany wcześniej fragment oryginalną teksturą). Zaimplementowałem funkcje do rysowania ładnego tekstu na ekranie (czcionkę miałem już gotową, zapisaną jako tablica bitowa w wygodnym do przetwarzania formacie), tryby gry (pomoc jak grać, gra, informacja o wygranej i przegranej). W związku z 3-godzinnym przesunięciem czasowym w stosunku do strefy, w której odbywał się konkurs, miałem czas do 3-ciej w nocy, więc mogłem pozwolić sobie na drobne doszlifowania samej grywalności i dokładniejsze dobranie parametrów symulacji. Niestety nie miałem pod ręką zawodowych beta-testorów, a nawet jakichkolwiek graczy, musiałem wyłącznie polegać na własnym wyczuciu. Z doświadczenia wiem, że to co mi się wydaje (jako twórcy) łatwe, dla innych graczy może być trudniejsze, dlatego tak dobrałem parametry symulacji oraz częstotliwość pojawiania się infekcji, aby trochę zmieniejszyć poziom trudności i gra była przystępniejsza dla większej liczby graczy. Poza konkursem dorobiłem 3 poziomy trudności (różniące się niektórymi parametrami, a głównie częstotliwością i ilością infekcji). Ale w tym opowiadaniu konkurs się jeszcze nie skończył! Pozostało mi już niewiele czasu, dosłownie piętnąście minut, napięta sytuacja, a nie ma jeszcze dźwięków! Na wszelki wypadek, przygotowałem już wersję bez dźwięków i wrzuciłem na serwer, w razie, gdybym nie zdążył. Na szczęście miałem w zanadrzu programik Kena (EvalDraw), w którym można było podać dowolną funkcję 1-wymiarową, zinterpretować i odsłuchać jako dźwięk, następnie zapisać na dysk. No więc do roboty. Trochę pamiętałem o syntezie FM, dźwięk alarmu "infekcyjnego" musiał być nieczysty, skleiłem trzy sinus'y, modulując jeden pozostałymi dwoma (sinus od dwóch sinusów), specjalnie dobierając ich okresy, aby nie współgrały ze sobą, wyszło całkiem zadowolająco, zapisujemy... Potem dźwięk pojawiających się wirusów z infekcji: sinus od czasu kwadrat doskonale się nadawał, takie blurrrbbb!, drobna modulacja, aby było ciekawiej i gotowe. Jeszcze tylko dźwięk wypuszczania antybiotyku, wyszło takie "połykanie", czyli dobrze (na początku niska częstotliwość, potem drobne dudnienie). Skróciłem wynikowe dźwięki, wyprofilowałem amplitudy w czasie i wrzuciłem do gry, wywołując w odpowiednich miejscach w kodzie, po prostu funkcję playsound(nazwa_pliku, głośność, częstotliwość). Zdążyłem! Zuploadowałem nową wersję, wrzuciłem wcześniej zrobione screenshoty, przez chwilę jeszcze nie mogłem opanować emocji i zadowolenia.

Ocenianie innych gier i wyniki końcowe

Jaki efekt? Po dwóch tygodniach etapu oceniania gier, moja gra została dosyć dobrze przyjęta przez innych, trochę nie odpowiadał większości sposób sterowania tabletką, być może bez tego mankamentu, uzyskał bym znacznie lepszy wynik. Poza tym, doceniono grafikę, niewielkie rozmiary pliku wykonywalnego (mimo, że nie było takiego wymagania) i oryginalny sposób grania (tabletka, wypuszczanie antybiotyku zamiast strzelania, żyjące komórki). Gry były oceniane niezależnie w różnych kategoriach. Zdobyłem pierwsze miejsce w kategorii zgodność z tematem, no i dosyć wysokie w pozostałych (w tym grafika, pomysłowość), w sumarycznej ocenie (kategoria "combo") uzyskałem 11-te miejsce. Wśród gier innych uczestników można było znaleźć na prawdę zabawne i grywalne produkcje! Etap oceniania jest bardzo przyjemny, bo możemy sobie pograć w mnóstwo małych, ciekawych, czasem bardzo nowatorskich gierek, przygotowanych przez innych. Każdy uczestnik może skomentować dowolną z nich, wyrazić słowami, to co mu się najbardziej podobało, a co według niego mogłoby być zrobione lepiej. Takie szczere opinie są bardzo przydatne w późniejszym dopracowaniu gry. Poza konkursem poprawiłem trochę sterowania, dorobiłem 3 poziomy trudności (o czym już wspominałem) i tryb demonstracyjny (powtórka z gry, łatwe do dodania dzięki stałej częstotliwości i deterministyczności symulacji). Po umieszczeniu poprawionej wersji na mojej stronie domowej, otrzymałem parę miłych listów od ludzi, którym podobała się moja gra, już w ostatecznej, dopieszczonej wersji.

Małe podsumowanie

Zachęcam wszystkich, aby przynajmniej raz spróbowali swoich sił w podobnych konkursach i zmierzyli się z najlepszymi z całego świata. Warto pamiętać też, że w przypadku gier komputerowych, najbardziej liczą się reakcje potencjalnych graczy, a same wyniki w podobnych konkursach, na pewno o czymś świadczą, ale chodzi przede wszystkim o dobrą zabawę, zarówno z samego procesu tworzenia, jak i z możliwości pokazania swojego dzieła innym (sprawienie innym trochę rozrywki i poddanie dzieła konstruktywnej krytyce). Warto też zauważyć, że różni ludzie mają różne talenty i umiejętności, jedni są bardziej nastawieni na techniczne apsekty gry, inni skupiają się na ładnej grafice i efektach dźwiękowych, niektórzy wyłącznie na grywalności. Dobrze sprawdził się system oceny według kategorii, zastosowany w konkursie, ponieważ zostały docenione indywidualne umiejętności twórców i każdy mógł się sprawdzić w tym, w czym czuje się najlepiej.

Grę Anti-Botic, można pobrać z mojej strony domowej:

http://ged.ax.pl/~tomkh/anti_en.htm

Program EvalDraw (wykorzystany do wygenerowania dżwięków), ze strony Kena Silvermana:

http://www.advsys.net/ken/download.htm#evaldraw

PS W ostatni weekend 17-18 października 2004, został zorganizowany kolejny, 5-ty konkurs http://ludumdare.com, w którym też wziąłem udział (podobnie jak Krzysztof Kluczek, kolega, który jest entuzjastą tego typu konkursów i zawsze mnie namawia, aby też startował :)) i znowu udało się zrobić coś ciekawego! W tej chwili uczestnicy mogą na siebie głosować, przyznawać sobie punkty i można sobie w końcu pograć :P

 


Aby dodawać komentarze musisz być zalogowany!


Kontakt

Jeśli chcesz się z nami skontaktować napisz na adres: info(at)binboy.org lub odwiedź nasz profil na Facebooku!

O Nas

Serwis binboy.org to kopalnia wiedzy dla wszystkich z branży IT, w szczególności dla programistów i webmasterów. To duży zbiór kursów programowania, tutoriali, darmowych ebooków, setki kodów źródłowych itp.

Bądź w kontakcie

Panel użytkownika

Zaloguj się do panelu użytkownika.
Nie masz konta? Zarejestruj się!
Zapomniałeś hasła?