implementacja gry komputerowej z interfejsem głosowym
TRANSCRIPT
str. 1
AKADEMIA GÓRNICZO-HUTNICZA IM. STANISŁAWA
STASZICA W KRAKOWIE
WYDZIAŁ INFORMATYKI, ELEKTRONIKI I TELEKOMUNIKACJI
INŻYNIERSKA PRACA DYPLOMOWA
IMPLEMENTACJA GRY KOMPUTEROWEJ Z INTERFEJSEM GŁOSOWYM
Autorzy: Konrad Jagielski, Bartosz Orliński
Kierunek studiów: Elektronika i Telekomunikacja
Opiekun pracy: dr inż. Bartosz Ziółko
Kraków, rok akademicki 2015/2016
str. 2
Oświadczamy, świadomi odpowiedzialności karnej za poświadczenie nieprawdy, że
niniejszą pracę dyplomową wykonaliśmy osobiście i samodzielnie (w zakresie
wyszczególnionym we wstępie) i że nie korzystaliśmy ze źródeł innych niż wymienione w
pracy.
str. 3
Serdeczne podziękowania dla dr inż. Bartosza Ziółko opiekuna pracy,
mgr inż. Szymona Pałki oraz mgr inż. Dawida Skurzoka,
za pomoc merytoryczną, wsparcie i motywację do pracy.
str. 4
Spis treści 1. Wstęp .................................................................................................................................. 5
1.1. Cel pracy.......................................................................................................................... 5
1.2. Streszczenie ..................................................................................................................... 5
2. Wprowadzenie .................................................................................................................... 6
2.1. Gry Komputerowe ........................................................................................................... 6
2.2. Hero Defense ................................................................................................................... 7
2.3. Rozpoznawanie mowy – ogólnie .................................................................................... 8
2.4. Rozpoznawanie mowy w grach ....................................................................................... 9
3. Opis narzędzi .................................................................................................................... 10
3.1. Unity3D ......................................................................................................................... 10
3.3 System kontroli wersji. ................................................................................................... 12
4. Opis projektu ........................................................................................................................ 12
4.1 Opis gry .......................................................................................................................... 12
4.2. Opis grafiki .................................................................................................................... 14
4.3. Implementacja SARMATY ........................................................................................... 17
4.4 Opis elementów .............................................................................................................. 19
4.4.1. Gracz ....................................................................................................................... 19
4.4.2. GameMaster ............................................................................................................ 22
4.4.3. Capzone .................................................................................................................. 22
4.4.4. Magia ...................................................................................................................... 24
4.4.5. Minotaur ................................................................................................................. 26
4.4.6 Przeciwnicy .............................................................................................................. 27
4.4.7. Interfejs użytkownika. ............................................................................................ 31
4.4.8 Kamera ..................................................................................................................... 36
4.5 Sposób podziału pracy – Metodyka Agile ..................................................................... 37
4.6 Fazy projektowania ........................................................................................................ 39
5. Zakończenie ...................................................................................................................... 40
5.1 Podsumowanie................................................................................................................ 40
5.2. Problemy ....................................................................................................................... 40
5.3. Przyszłość projektu........................................................................................................ 41
str. 5
1. Wstęp
Niniejsza praca inżynierska jest wynikiem wspólnej pasji jej autorów. Zamiłowanie do gier
komputerowych nie było oczywiście jedyny czynnikiem wpływającym na wybór tematu.
Istotna rolę odegrała także chęć rozwoju umiejętności programistycznych w języku
programowania C#, którego poznanie stało się kolejnym krokiem rozwoju zawodowego
autorów jak również poznanie sposobu działania silnika Unity3D, a także pracy w nim.
Ciekawą wydawała się również perspektywa projektowania własnego oprogramowania
oraz wypróbowania w praktyce poznanej metodyki Agile jako sposobu zarządzania.
1.1. Cel pracy
Celem pracy było stworzenie gry komputerowej sterowanej nie tylko za pomocą
klasycznego sterowania klawiaturą i myszą, ale także z wykorzystaniem interfejsu
głosowego. Gracz miał kontaktować się z grą korzystając z mikrofonu dzięki
wykorzystaniu systemu rozpoznawania mowy. Pobocznym celem pracy było sprawdzenie
czy podobne rozwiązanie ma sens funkcjonowania na rynku komercyjnym, a także
pokazanie procesu twórczego towarzyszącego kreacji gry komputerowej.
1.2. Streszczenie
Poniższa praca inżynierska jest opisem implementacji gry komputerowej, mającej na celu
zademonstrowanie wykorzystania interfejsu głosowego opartego o rozpoznawanie mowy
polskiej przy użyciu systemu SARMATA stworzonego przez Zespół Przetwarzania
Sygnałów Akademii Górniczo-Hutniczej[1]
. Przedstawiono krótki opis wybranego gatunku
gier, a także historię wykorzystania rozpoznawania mowy w grach komputerowych.
Dalsza część pracy przedstawia również złożony opis silnika Unity3D w którym została
stworzona opisywana gra. W pracy opisano także główne klasy wykorzystywane w grze.
W związku z faktem tworzenie projektu w dwuosobowym zespole przedstawiono również
sposób dzielenia pracy oraz opisano poszczególne etapy jej powstawania. W części
kończącej pracę poza podsumowaniem opisano pojawiające się w trakcie implementacji
problemy, a także zaproponowano dalsze możliwości rozwoju projektu.
str. 6
2. Wprowadzenie
2.1. Gry Komputerowe
Gry komputerowe podobnie jak inne formy zabawy opiera się o podstawowy cel,
wybierany przez gracza bądź narzucany przez twórców. Graczowi dostarczana jest
rozrywka poprzez pokonywanie stawianych przez niego wyzwań. Kluczowym elementem
gry komputerowej jest całość oprawy audiowizualnej. Celem twórcy jest więc zapewnienie
graczowi długich godzin jak najciekawszej formy rozrywki poprzez stawianie na jego
drodze grafiki i dźwięku opartych o pewną narracje.
Gry komputerowe powstały w połowie XX wieku jako produkcje akademickie. Następnie
głównie za sprawą amerykańskiego (Atari) i japońskiego (Nintendo) przemysłu gry
komputerowe stały się elementem rozrywki masowej. Mimo, że początkowo gry
wydawane były głównie na dedykowane konsole to ich rozwój jest nierozerwalnie
złączony z rozwojem komputerów osobistych. W latach 90. XX wieku zaczęły powstawać
pierwsze produkcje o grafice imitującej trójwymiarową, z których niektóre stały się
później kanonem klasyki gier komputerowych. XXI wiek przyniósł branży ogromne
możliwości. Szerokopasmowy Internet pozwolił na wprowadzenie w grach trybów
wieloosobowych, a co za tym idzie elementów socjalizacji. Przyciągnęło to nie tylko
miliony graczy, ale również gigantyczne pieniądze. Zgodnie z raportem ESA-Essential-
Facts[2]
w samych Stanach Zjednoczonych w szeroko pojęte gry wideo w 2014 roku grało
około 155mln mieszkańców. Należy również wspomnieć, że najlepiej sprzedające się
produkcje przynoszą twórcą i wydawcą ogromne dochody. Przykładowo seria Call Of
Duty sprzedaje się rokrocznie w około 20 milionach egzemplarzy, przynosząc wydawcy
zyski rzędu kilkuset milionów dolarów już w ciągu pierwszych godzin od premiery[3]
.
Gracze stają się jednak bardziej świadomi, podobnie jak w innych dziedzinach rozrywki w
wyniku przesycenia rynku wielkimi produkcjami, coraz częściej sięgają po produkcje
niszowe, dostępne za mniejsze pieniądze, a dostarczające zabawy na długie godziny.
Mamy, więc dzięki temu (jak również dzięki firmie Valve Corporation i jej platformie
Steam) do czynienia z intensywnym rozwojem produkcji niezależnych (Indie Games). Nie
wszystkie oczywiście można zaliczyć do udanych. Jednak obserwacja powstających
produkcji pozwala na wyznaczanie trendu panującego na rynku.
Twórcy coraz częściej sięgają po rozwiązania alternatywne np. w dziedzinie sterowania.
Największe firmy w branży sięgają po sterowanie gestem czy różnego rodzaju sensorami.
Nieco zaniedbywany jest koncept sterowania przy pomocy głosu co stało się niejako
powodem powstania tej pracy.
str. 7
2.2. Hero Defense
Aby rozgrywka nie była monotonna każda gra powinna posiadać cel oraz element
współzawodnictwa. Jeden typ takich gier nazwany jest w języku angielskim „Hero
Defense” co można luźno przetłumaczyć na język polski jako „bohaterska obrona”. Jego
celem jest obrona obszaru lub budynku przed napierającymi kolejnymi falami wrogów.
Prekursorem tego gatunku jest stworzona jako mapa społeczności – czyli wymyślona oraz
zbudowana w całości przez graczy za pomocą udostępnionych wewnętrznych narzędzi - w
grze wydanej na komputery osobiste, Warcraft 3.
Pierwszą decyzją gracza był wybór bohatera – każdy z nich różnił się posiadanymi
zdolnościami specjalnymi, czarami oraz specyfikacją swoich atrybutów odpowiadających
między innymi za ilość życia, siłę i szybkość ataku czy maksymalną liczbę rzuconych
czarów. Następnie był on rzucany w ferwor walki, gdzie miał do pokonania trzy
przeszkody – uciekający czas, innych graczy oraz nieskończone morze przeciwników. Co
określoną chwilę pojawia się kolejna fala przeciwników, za każdym razem rosnąca w siłę.
Gracze muszą nie dopuścić przeciwników do dotarcia w określone miejsce na mapie,
jednocześnie współzawodnicząc ze sobą w ilości pokonanych wrogów.
Rysunek 1 Screen z gry Warcraft 3 obrazujący mapę typu Hero Defense[4]
Jak wiele map tego typu Hero Defense doczekało się swojego wydania w wersji
niezależnej od Warcraft 3. Jedna z nowszych produkcji tego typu o nazwie Bloodsports.TV
str. 8
została wydana w marcu 2015 roku, jednak nie zebrała dobrych recenzji i ciągle nie cieszy
się popularnością[5]
.
Gry typu Hero Defense wydają się być przytłoczone przez podobny typ gier, również
powstałych jako modyfikacja do Warcraft 3 o nazwie DOTA – MOBA[6]
(z ang.
Multiplayer Online Battle Arena). Różnica między nimi polega na tym, iż gracze stoją po
obu stronach konfliktu, wspomagając kolejne fale jednostek dążą do zniszczenia głównego
budynku przeciwnika. Powodem tej nierównowagi może być fakt, iż walka ze sztuczną
inteligencją nie przynosi graczom takiej rozrywki i dozy przyjemności jak
współzawodniczenie z żywym przeciwnikiem.
Powstało również wiele aplikacji na platformę android, które realizują rozgrywkę tego
typu. Wiele z nich przenosi gracza w świat dwuwymiarowy, co ułatwia kontrole nad
postacią na ekranie dotykowym. Według danych z play.google.com najpopularniejsze gry
tego typu pobierane są między sto a pięćset tysięcy razy[7]
.
2.3. Rozpoznawanie mowy – ogólnie
Rozpoznawanie mowy to technologia polegająca na interpretacji mowy przez urządzenie.
Ze względu na niską absorbcje uwagi pozwala na przykład na sterowanie urządzeniami bez
konieczności użycia kończyn, może być też szczególnie przydatna dla osób
niepełnosprawnych jak i jako narzędzie transkrypcji. Obecnie istnieje wiele systemów
rozpoznających mowę w różnych językach, systemy te wciąż posiadają jednak wiele wad
wynikających z różnego rodzaju problemów implementacyjnych. Sygnały wejściowe
charakteryzują się dużą zmiennością, mówcy mówią w różny sposób, mają różne dialekty
czy różne tempo wypowiedzi. Jednak problemy hamujące rozwój technologii
rozpoznawania mowy wynikają nie tylko z aspektów technicznych, ale także z pewnych
limitacji wdrożeniowych. Używanie sterowania głosowego jest ograniczone ze chociażby
ze względu na fakt, że różni użytkownicy mogą przeszkadzać sobie nawzajem.
Rozwiązywanie tych problemów trwa od dziesięcioleci, a wciąż jest jeszcze wiele do
zrobienia.
W rozważaniach o rozpoznawaniu mowy należy zwrócić uwagę na historię powstawania
kolejnych elementów koniecznych nie tylko do samej cyfrowej analizy, ale również do
samego pozyskania próbek głosu czyli po prostu jego nagrania. Niewątpliwie ważną rolę w
tej dziedzinie odegrał Aleksander Graham Bell który w latach osiemdziesiątych XIX
wieku zbudował telefon tworząc podwaliny dzisiejszej techniki przesyła głosu za pomocą
słuchawki i mikrofonu. W 1920 roku zbudowano prymitywny system reagujący na imię
psa: "Rex", a dokładniej głoskę "e" zintegrowany z psią budą i zabawkowym psem –
system nazywał się tak jak pies i reagował na charakterystyczną zmianę częstotliwości
towarzyszącą głosce[8]
. W latach pięćdziesiątych XX wieku Bell Labs zademonstrował
AUDREY maszynę rozpoznającą cyfry. Rozwojowi maszyn rozpoznających głos
towarzyszył rozwój słowników. Obecnie kiedy dysponujemy ogromnymi możliwościami
jeśli chodzi o pozyskiwanie i przechowywanie danych wielkie korporacje z łatwością
str. 9
mogą pracować nad takimi systemami. Jednym z najbardziej rozpowszechnionych (bo
dostępnym między innymi w niemal każdym nowym smartfonie z systemem Android)
systemem rozpoznawania mowy jest Google Speech Api[9]
. W 2011 roku kalifornijski
gigant dysponował słownikiem języka angielskiego z około milionem różnych słów.
Obecnie dzięki tym systemom możemy dyktować treść wiadomości prowadząc samochód
czy korzystać z wyszukiwarki internetowej bez konieczności korzystania z rąk. Mimo, iż
najlepiej rozpoznawany jest język angielski firma wprowadza również słowniki innych
języków. System ma jednak wiele wad jest dość wolny, ponieważ stosuje moduły uczenia
maszynowego oraz niedokładny co ma jednak dzięki rozwojowi sprzętu i metod ma stać
się bardziej użyteczny. W pracy korzystano z systemu rozpoznawania mowy
SARMATA[1]
stworzonego przez zespół DSP.
2.4. Rozpoznawanie mowy w grach
Przemysł gier komputerowych ciągle poszukuje nowych pomysłów jak wzbogacić
rozgrywkę oraz jak sprawić, by gracz czuł się w wirtualnym świecie jak w prawdziwym
życiu. Powstało wiele interfejsów, pozwalających na interakcje człowieka z grą.
Najbardziej popularnymi są klawiatura komputera w połączeniu z myszką, pady
stosowanie głównie na konsolach oraz zestawy osprzętu samochodowego, lub mapy
taneczne. W ostatnich latach na rynku, w raz z rozwojem techniki pojawiają się coraz
bardziej złożone interfejsy, jak na przykład sensory ruchu bazujące na kamerach video –
Kinect od Microsoftu, czy PlayStation Move od Sony. Naturalnym sposobem wymiany
informacji między ludźmi jest rozmowa. Ludzki umysł przystosowany jest do takiego
tempa i sposobu przyswajania wiedzy, co wynika z wielu lat ewolucji.
Coraz więcej producentów gier zdaje się zauważać fakt, iż możliwe jest połączenie gracza
z ich wirtualnymi awatarami za pomocą interfejsu głosowego. Wydając polecenia głosowe
można tworzyć scenariusze gier, w których całość świata sterowana jest za pomocą
komend lub są one dodatkiem do istniejących już kontrolerów.
Jednym z przykładów gier, gdzie całkowita kontrola sprawowana jest za pomocą komend
głosowych jest There Came An Echo[10]
. Gra została stworzona od podstaw z zamysłem
wykorzystania takiego interfejsu. Gracz wciela się w niej w dowódcę niewielkiego
oddziału, któremu wydając polecenia próbuje przejść kolejne poziomy i ukończyć wątek
fabularny. Do dyspozycji została oddana cała gama poleceń, odnoszących się zarówno do
poszczególnych członków drużyny, jak i do niej całej.
Na planszy umieszczono specjalne znaczniki, z których każdy posiada indywidualną
nazwę. Używając ich w rozkazach gracz może przemieścić swoich podwładnych lub
przeprowadzić ostrzał danej pozycji. Każda z postaci posiada własny zestaw broni, do
których użycia zachęca się poprzez użycie ich nazw w poleceniu. Gra jest sprzedawana na
platformie Steam, gdzie niestety nie cieszy się dużą popularnością. Z 244 recenzji
pozytywnych jest 75%[11]
, co pozwoliło Steam’owi ocenić je na „W większości
pozytywne” (stan na dzień 25.12.2015). Gra nie obsługuje mowy polskiej.
str. 10
Kolejna grą, w której wymiennie można stosować polecenia głosowe, jak i klasyczne
sterowanie za pomocą klawiatury i myszki to Tom Clancy’s EndWar. Tutaj również,
podobnie jak w There Came An Echo gracz wciela się w dowódcę, tym razem całej armii.
Każda z jednostek posiada indywidualne oznaczenie, odnosząc się do którego można
wydać jej odpowiednie rozkazy – zaatakowanie wrogiej jednostki, zajęcie punktu
kontrolnego czy rekrutacja nowych żołnierzy. Gra została oceniona w bardzo podobny
sposób jak opisywana wcześniej – na portalu Steam ze 171 recenzji pozytywnych było
74%[12]
. Ocena to również „W większości pozytywne”. Mowa polska nie jest obsługiwana.
Dlaczego gry tego typu nie zdobyły popularności? Możliwe, że odpowiedź na to pytanie
kryje się w sposobie, w jaki człowiek odbiera i przekazuje informacje mówione. Znaczenie
ma tutaj nie tylko sama treść informacji, lecz także sposób w jakim zdanie zostało
wypowiedziane. Mimika twarzy i gestykulacja również odgrywają znaczącą rolę w
komunikacji interpersonalnej. Komputer pozbawiony osobowości jest w stanie tylko
reagować na wykryte czynniki lub przeprowadzać z góry zaplanowane akcje, co jest
natychmiast wychwytywane przez człowieka i sprawia wrażenie nienaturalnego.
Jednak samo nasycenie emocjonalne głosu może również zostać wykorzystane w grach
wideo, jako kolejny czynnik odpowiadający na przykład za szybkość pojazdu, moc czaru
lub chęć z jaką wirtualna postać wykona zlecone przez gracza zadanie.
3. Opis narzędzi
3.1. Unity3D
Unity3D[24]
to jeden z największych dostępnych za darmo edytorów gier komputerowych.
Edytor jest wyposażony w własny silnik graficzny, dźwiękowy oraz symulator fizyki.
Unity pozwala na tworzenie gotowych gier na różne platformy zarówno w 3D jak i w 2D.
Dostarcza także gotowe funkcje do tworzenia i łączenia animacji czy też obróbki i
planowania rozgrywki. Środowisko jest bardzo rozbudowane jednak aby stworzyć grę w
Unity3D nie potrzeba kilkuletniego doświadczenia w programowaniu czy w korzystaniu z
samego edytora. Wystarczy zrozumieć podstawowe pojęcia i nauczyć się poruszać w
interfejsie i na podstawie poradników w Internecie oraz gotowych paczek z dodatkami
można stworzyć grę. Środowisko nie jest jednak używane tylko przez twórców
niezależnych, ale także przez duże studia, są w nim tworzone setki gier dzięki czemu
bardzo szybko się rozwija. Uaktualnienie do wersji obecnej piątej już generacji było
dużym skokiem w rozwoju silnika. Wprowadzono wersje 64-bitową, a także wiele zmian
w obsłudze animacji i dźwięku. Dzięki temu w nowej wersji Unity3D tworzone są między
innymi takie produkcje jak Hearthstone, Wasteland 2, Cities: Skylines czy DeusEx:The
Fall.
str. 11
Rysunek 2 Screen edytora Unity
Interfejs Unity składa się z wielu dostępnych widoków. Najistotniejsze to "Hierarchia",
"Scena", "Inspektor" oraz "Gra". Po odpowiednim rozmieszczeniu widoków w głównym
oknie edytor jest gotowy do tworzenia gry. Proces twórczy odbywa się poprzez
przeciągnie elementów na scenę i edytowanie ich komponentów w oknie inspektora. Na
scenie deweloper operuje w trójwymiarowym układzie współrzędnych. Każdy obiekt
posiada swoje współrzędne zawarte w wbudowanym w Unity komponencie typu
Transform. Z punktu widzenia dewelopera programisty najważniejszym komponentem jest
skrypt. Do każdego elementu na scenie możemy dołączyć komponent skryptu zawierający
dowolne określone w kodzie zachowania mające być realizowane przez dany obiekt.
Twórcy silnika dostarczyli jednak bardzo wiele gotowych komponentów pozwalających
między innymi na symulacje fizyki czy renderowanie grafiki.
Każdemu typowi w kodzie odpowiada klasa, każdemu komponentowi instancja tej klasy. Z
poziomu programisty poruszanie się po obiektach na scenie jest bardzo łatwe. Każda klasa
odpowiadająca za zachowanie danego obiektu automatycznie dziedziczy z klasy
MonoBehaviour której metody pozwalają na wyszukania referencji każdego komponentu
w danym obiekcie oraz jego hierarchicznych dzieciach. MonoBehaviour posiada też
funkcje wywoływane automatycznie w odpowiednich momentach pozwalające na
wykonywanie się określonych przez twórcę czynności. W projekcie użyte zostały funkcje
Start() – wywoływana w pierwszej klatce w której obiekt staje się aktywny, Update() -
zawsze na początku każdej klatki, LateUpdate() - na końcu każdej klatki oraz
OnTriggerEnter() – wywoływana jednorazowo w momencie przejścia obiektu
transparentnego przez inny obiekt czy OnColiderEnter() – w momencie kolizji obiektów
fizycznych. Dzięki czemu programowanie w edytorze jest bardzo proste.
str. 12
Unity3D w ramach współpracy z firmą Microsoft dostarcza dodatek dla Visual Studio
pozwalając na debugowanie kodu w trakcie gry, a także połączenie z serwerem kontroli
wersji.
3.3 System kontroli wersji.
Niezależnie od wielkości czy typu projektu korzystanie z systemu kontroli wersji pozwala
osobom w niego zaangażowanym na łatwe zarządzanie kolejno wprowadzanymi
zmianami. Korzysta on z tak zwanych repozytoriów, czyli nadrzędnych zbiorów kodu i
historii jego zmian. Każde repozytorium składa się z co najmniej jednej gałęzi (ang.
branch), która pozwala zrównoleglić proces wprowadzania zmian. Takie udogodnienie
sprawia, iż gdy zajdzie taka potrzeba można zachować aktualnie wprowadzane zmiany i
powrócić do nich później.
System kontroli wersji sprawdza się doskonale do scalania kodu pisanego przez wiele
osób. Po wykonaniu przydzielonego zadania oraz przejściu przez system recenzji
programista może wprowadzić swoją zmianę do wspólnego repozytorium. Tego typu
system pozwala odnieść się do dowolnego momentu istnienia kodu, poprzez przywołanie
konkretnej zmiany, a także daje możliwość wycofania dowolnego ich podzbioru. Jest to
naturalny wybór, gdy zachodzi potrzeba pracy w metodykach zwinnych. W pracy
korzystano z systemu dostarczanego przez firmę Microsoft[25]
.
4. Opis projektu
4.1 Opis gry
Stworzona w ramach pracy inżynierskiej gra zawiera elementy Hero Defense. Osadzona w
realiach fantasy bitwa pomiędzy samotnym magiem – wirtualną reprezentacją gracza -
oraz hordą przeciwników mających tylko jeden cel – dotrzeć za wszelką cenę do
bronionego przez gracza obiektu.
W grze rozróżnione są trzy typy przeciwników, każdy z nich posiada odmienne
właściwości i gracz powinien użyć innej taktyki, w zależności od bliskości danego typu
wrogów oraz ich ilości w danym obszarze. Celem gry jest jak najdłuższe przetrwanie
gracza, który ograniczony jest jedynie ilością zdrowia, traconą po każdorazowym dotarciu
wroga do strefy zamkniętej, bronionej przez bohatera. Z każdym zabitym wrogiem gracz
otrzymuje punkty mnożone dodatkowo przez poziom wzrastający co określoną ilość czasu.
str. 13
Rysunek 3 Screen z gry z widocznym interfejsem
Gracz ma możliwość poruszania się w trójwymiarowym świecie. Aby nie dopuścić
wrogów do celu musi ich niszczyć za pomocą szerokiego wachlarza czarów oraz swojego
pomocnika – chowańca minotaura.
Sterowanie w grze zrealizowane jest za pomocą hybrydy sterowania klasycznego – myszki
i klawiatury – oraz interfejsu głosowego. Każdy z czarów realizowany jest za pomocą
wypowiedzenia dwóch słów – kształtu czaru oraz jego żywiołu. Minotaur reaguje na
polecenie ataku po uprzednim umieszczeniu przez gracza znacznika na odpowiednim
wrogu.
Jeśli zbyt wiele przeciwników dostanie się do bronionego miejsca, gracz umiera i gra się
kończy.
Wszystkie postacie zostały wygenerowane za pomocą silnika Mixamo[26]
należącego do
Adobe. Aktualnie modele nie są już dostępne, akcja mająca na celu promocję silnika
zakończyła się w trakcie trwania wakacji 2015. Podczas jej trwania, każde konto było
uprawnione do użycia dowolnej liczby modeli z szerokiej puli dostępnych jako demo.
Ograniczenie polegało na fakcie, iż całkowita liczba animacji połączonych z modelami nie
mogła przekraczać dwudziestu.
Do stworzenia gry użyto silnika Unity3D, ze względu na jego elastyczność, powszechność
materiałów szkoleniowych oraz przejrzystość dokumentacji.
Jako system rozpoznawania mowy polskiej wykorzystano Sarmatę rozwijanego przez
Zespół Przetwarzania Sygnałów z krakowskiej Akademii Górniczo-Hutniczej[1]
.
str. 14
4.2. Opis grafiki
Jednym z głównych elementów gry jako rozrywki multimedialnej jest jej grafika. W
trakcie tworzenia komercyjnych gier komputerowych nad ich wyglądem pracują
najczęściej wielkie grupy artystów projektantów grafiki jak i animatorów. W projektach
wielkich serii takich jak Assasins Creed, Call of Duty których kolejne edycje wydawane są
co roku zespoły grafików liczą po kilkadziesiąt osób. W projekcie z racji bycia pracą
inżynierska, a nie komercyjną czy artystyczną skupiono się na aspektach technicznych
czyli głównie programistycznym podejściu do gier komputerowych. Początkowo świat gry
miałby płaski i klinicznie pusty co zwiększało jego czytelność, jednak w miarę dodawania
kolejnych elementów zwłaszcza po uzyskaniu kompletnych modeli odznaczających się
dokładnym detalem uznano, że należy go wypełnić. W tym celu skorzystano w tworzonego
przez społeczność Unity i wspieranego przez twórców sklepu AssetStore na którym można
znaleźć wiele zarówno darmowych jak i płatnych elementów graficznych. Można znaleźć
tam bardzo wiele elementów jednak mimo dużej swobody w dodawaniu poszczególnych
dodatków twórcy zawsze stawiają na spójność świata. Podjęto więć decyzje o stworzeniu
spójnego świata jednak koncepcja musiał być na tyle abstrakcyjna aby móc pozwolić sobie
na dużą dowolność w produkcji.
Świat gry to świat magiczny, co daje możliwość rezygnacji z realizmu i
wiarygodności poszczególnych elementów. W standardowej pozycji kamery gracz widzi
teren aż po sam horyzont w związku z tym początkowo w oczy rzucała mu się płaskość
świata i pustka na używanym wówczas niebie. Postanowiono więc umieścić na mapie niski
ale szeroki las. Drzew skutecznie wypełniły pusty obszar jednak otoczenie nimi całej strefy
wydało się przesadnie nudne, dlatego wypełnił on tylko jeden narożnik. Sam jednak
zielony las w połączeniu z zieloną trawą i niebieskim niebem nie miały w sobie nic
magicznego, więc atmosfera magii w którą chciano wprowadzić gracza nie dala się
poczuć. Ze względu na fakt, iż przy tworzeniu pierwszych pocisków używano gotowych
efektów cząsteczkowych, które dobrze odwzorowywały żywioły, a także dawały poczucie
nie realizmu charakterystycznego dla magicznego świata uznano, że wypełnienie nimi
świata gry będzie dobrym pomysłem. W lesie umieszczono więc emitery kolorowych
promieni, a cały świat pokryto mgłą składającą się z ogromnej ilości cząsteczek, która
wraz ze zredukowanym oświetleniem wprowadziła mroczną atmosferę. Aby domknąć
nierealność lasu umieszczono w nim nienaturalnej wielkości grzyby oraz ogromne
wyschnięte drzewa. Fragment lasu została przedstawiona na ilustracji poniżej.
str. 15
Na ilustracji poza lasem możemy zauważyć zmieniony teren oraz imitacje czarnego
nieba z fioletowymi gwiazdami. Postanowiono, że naturalne słoneczne oświetlenie nie
pasuje do abstrakcyjnego świata dlatego światło białe zastąpiono fioletowym, a niebieskie
dotychczas niebo czarnym. Pustkę nieba uzupełniono uzyskanymi z AssetStore
elementami uzyskanymi z paczki Vast Outer Space[13]
. Paczka ta zawierała gotowe
przygotowane obiekty będące odwzorowaniem ciał niebieskich. Powiększono je do
ogromnych rozmiarów u umieszczono w odpowiedniej odległości, aby widoczne były na
horyzoncie w trakcie gry. Pozwoliło to wypełnić kolejny pusty narożnik mapy.
Rysunek 4 Screen z gry - Las
Rysunek 5 Screen z gry - Ciała niebieskie
str. 16
Unity dostarcza bardzo zaawansowane narzędzie do modyfikacji terenu. Działa ono
z wykorzystaniem pędzli. Twórca ma do dyspozycji pędzle służące podnoszeniu i
obniżaniu terenu, wygładzaniu i kolorowaniu, a także dwóm pędzlom służącym stawianiu
drzew oraz traw i kwiatów. Każdy z pędzli może wykorzystywać zaimportowane bądź
narysowane własnoręcznie paczki z elementami. Szczególnie interesujące są jednak dwa
ostatnie pędzle w ich ustawieniach możemy dobrać zakresy losowania parametrów drzew,
pozwala to uzyskać las w którym wszystkie drzewa różnią się od siebie, możemy podać
skale barwienia roślin, sterować ich wyschnięcie, a wreszcie podać zakresy zmienności ich
wysokości i szerokości. Za pomocą dwóch paczek Fantasy Forest[14]
oraz Painterly
Nature[15]
udało się uzyskać gotowy las, a także trawy i krzaki znajdujące się na terenie
mapy. Tekstura terenu jest stylizowana na popularne w grach motywy pustkowia. Dzięki
pędzlowi służącemu do zmiany wysokości terenu stworzono wzgórze, w którym w celu
urozmaicenia osadzono drzewo.
Fioletowe oświetlenie, mgła oraz czarnofioletowy skybox stworzyły atmosferę
magicznego surrealistycznego świata zostały dopełnione nienaturalnymi drzewami oraz
różnobarwnymi trawami i drzewami, a także ogromnymi planetami. Całość wygląda jest
dosyć spójną magiczną kompozycją, a wraz z dynamicznym oświetleniem dostarczanym
przez Unity oraz efektami cząsteczkowymi tworzy dynamiczny świat z dosyć płynną
akcją.
Rysunek 6 Screen z gry - wzgórze
str. 17
4.3. Implementacja SARMATY
Kluczowym elementem pracy, było skorzystanie z funkcjonalności Sarmaty – systemu
rozpoznawania mowy polskiej. W zamyśle, za pomocą interfejsu głosowego gracz mógł
wywoływać kolejne zaklęcia, które składały się z dwóch członów: żywiołu oraz kształtu.
W wyniku rozpoznania komendy, bohater gracza zaczyna dysponować odpowiednią
magią. Dodatkowo wprowadzono komendę ataku, pozwalającą wydawać polecenie szarży
pomocnikowi minotaurowi.
Implementacja interfejsu Sarmaty miała zostać napisana przy wykorzystaniu otrzymanej
od zespołu DSP biblioteki klienta skompilowanej w zgodności z platformą .NET. Unity od
wersji 5.0 pozwala na wykorzystanie bibliotek zewnętrznych jako zarządzalnych w wersji
darmowej silnika, dzięki temu implementacja miała być bardzo łatwa, a kod miał być
kompilowany w Unity razem z grą. Podczas importowania biblioteki pojawił się jednak
problem związany z wersją platformy .Net. Silnik ogranicza użycie bibliotek .Net do
wersji 2.0, niestety otrzymana biblioteka mimo iż jest skompilowana w zgodności z
odpowiednią wersją wykorzystuje podczas działania nowszą wersje przez co Unity
rozpoznaje ją jako natywną i nie pozwala na jej swobodne wykorzystanie. Możliwym jest
zawołanie metod natomiast nie jest możliwe korzystanie z klas, tworzenie instancji i
wykorzystanie typów zawartych w bibliotece.
Z tego powodu zdecydowano się na rozwiązanie mniej eleganckie, ale dające możliwość
spełnienia założeń projektu. Stworzono samodzielny program obsługujący sesję Sarmaty,
który gdy zachodzi potrzeba jest uruchamiany. Jest w pełni konfigurowalny w
podstawowym zakresie przez plik zawierający ścieżki do nagranego głosu oraz gramatyki.
Podając ścieżki należy pamiętać, iż nie są one bezwzględne – domyślnie przeszukiwany
jest katalog zawierający plik wykonywalny Sarmaty.
Gramatyka zawiera nagłówek ANBF[17]
(Augmented Backus–Naur Form) w wersji 1.0,
który zawiera informację o jej języku, używanym trybie oraz wskazanie na korzeń
tworzonego drzewa.
#ABNF 1.0;
language pl;
mode voice;
root $czar;
Następnie zgodnie ze standardem ANBF następuje wskazanie kolejnych oczekiwanych
słów wraz z ich logicznym połączeniem. Znak dolara oznacza zdeklarowanie zmiennej
nazwanych w tym formacie zasadami (ang. rule), których definicja odbywa się przez
dwuargumentowy znak równości. Znak alternatywy pozwala na stworzenie listy słów, z
których wybrane zostanie tylko jedno. Znaki nawiasów kwadratowych zostały użyte aby
str. 18
wskazać, które części tworzonego poprzez kolejne referencje do zasad drzewa są uznane
jako alternatywne. Każda z linii zakończona jest przez średnik.
$czar = $prefix [$postfix] | $atak;
Oznacza iż wypowiedziana przez gracza fraza, która została przesłana do rozpoznania
będzie zawierać czar składający się z obowiązkowego z prefixu oraz opcjonalnego
postfixu albo pojedynczą komendę ataku. Opcjonalność prefixu została jednak pominięta i
moduł decyzyjny gry wymaga obu słów podczas wyboru zaklęcia.
Właściwy program interfejsu Sarmaty po uruchomieniu przechodzi do wspomnianego już
pliku konfiguracyjnego, pobierając z niego nazwy i ścieżki plików zawierających nagrany
dźwięk oraz gramatykę. Następnie korzystając z konstruktora klasy RemoteSession
odpowiedzialnej za nawiązanie i utrzymanie sesji z serwerem, przyjmującego jako
argument string w formacie adres_IP:port tworzone jest połączenie. Jeśli z jakiś powodów
proces ten zakończył się niepowodzeniem zostanie wykorzystany mechanizm obsługi
wyjątków, który wypisze na standardowym wyjściu odpowiedni komunikat błędu.
Następnie wczytywana jest gramatyka poprzez metodę wczytajGramatyke, według ścieżki
podanej w pliku konfiguracyjnym. Następnie poprzez metody klasy RemoteSession
kolejno przekazywana jest do sesji (SetGrammar) oraz ustanawiana jest sama sesja
(StartSession).
Kluczowym elementem algorytmu jest zastosowanie delegata, czyli wywodzącej się z C++
idei wskaźnika na funkcję/metodę, która w C# została rozwinięta o bezpieczeństwo
zwracanego typu oraz możliwość zwracania również obiektów. Ograniczeniem delegatów,
jest przymus wskazywania na metody zwracające oraz przyjmujące jako argumenty
jednolite typy. Delegatem zastosowanym w dynamicznie linkowanej bibliotece jest
OnASREventDlg, który jako argument przyjmuje metodę obsługującą zdarzenia. Taką
metodą jest target z klasy Program przyjmującą jako argument obiekt klasy
ASREventArgs dziedziczącej z EventArgs czyli klasy bazowej .Net zawierającej
informacje o zdarzeniu. Metodę target należy również dodać do listy przerwań instancji
klasy RemoteSession – OnEvent. Gdy interfejs Sarmaty otrzyma odpowiedz na żądanie,
wykonywane jest zaplanowane zdarzenie – w tym wypadku metoda target, która w
zależności od otrzymanego jako argument typu wyliczeniowego EventType wypisuje na
standardowym wyjściu odpowiednio rozpoznane słowo lub błąd wraz z jego kodem.
Rozpoznana fraza przekazywana jest również przez obiekt klasy ASREventArgs, a
mianowicie jego pole Phrases[0].RecognizedWords;
Następnym krokiem jest załadowanie gotowego nagrania głosowego w formacie wave
próbkowanego z częstotliwością 16 kHz, opisanego na 16 bitach, posiadający tylko jeden
kanał mono. Format wave opiera się w wersji podstawowej na nagłówku o długości 40
bajtów oraz ciągu niezakodowanych próbek. Do programu wczytywany jest sam ciąg liczb
reprezentujących dźwięk, z pominięciem nagłówka. Mając pewność, iż nagranie jest w
pełni skończone wywoływane są kolejno metody ProcessSamples obiektu klasy
RemoteSession przyjmująca jako argument wektor próbek w formacie short, która
str. 19
odpowiedzialna jest za wysłanie ciągu próbek na adres serwera Sarmaty wraz z poleceniem
rozpoczęcia procesu rozpoznawania mowy oraz bezargumentowa metoda Flush obiektu tej
samej klasy, która informuje system dopasowujący, iż wszystkie próbki zostały
rozpoznane i interfejs oczekuje na jak najszybszą odpowiedź.
Aby połączyć stworzony interfejs Sarmaty z grą implementowaną w Unity3D stworzono
funkcjonalności oparte na klasie skryptMikrofonu. W metodzie start, uruchamianej przy
stworzeniu instancji danej klasy przeprowadzana jest detekcja urządzenia
przetwarzającego głos. Odbywa się to z wykorzystaniem klasy Microphone, która
automatycznie sprawdza podłączone urządzenia. Jeśli choć jedno zostaje znalezione
częstotliwość jego próbkowania jest ustawiana na 16 kHz oraz definiowany jest obiekt
klasy AudioSource jako właśnie mikrofon. Po wykryciu wciśnięcia klawisza
odpowiedzialnego za rozpoczęcie sekwencji wydawania polecenia, rozpoczynane jest
nagrywanie próbek, trwające 2 sekundy. Proces ten odbywa się w Coroutine. Po
zakończeniu nagrywania następuje zapis do pliku. Aby uzyskać pożądany format
wykorzystano gotowy kod napisany przez Calvina Riena[16]
. Tworzy on zarówno nagłówek
zgodny ze standardem wave o długości 44 bajtów, wycina fragmenty ciszy aby zmniejszyć
rozmiar pliku oraz co najważniejsze dokonuje konwersji z próbek typu float do 16
bitowych.
Algorytm zamiany polega na przemnożeniu każdej próbki przez stałą równą 32767 i
rzutowaniu typu wyniku na short. Reprezentacja obiektu 16 bitowego realizowana jest za
pomocą tablicy dwóch bajtów. Za konwersję z liczby typu short na 16 bit odpowiada klasa
BitConverter oraz jej metoda GetBytes.
Za uruchomienie interfejsu sarmaty w nowym procesie odpowiedzialna jest klasa
System.Diagnostics.Process. Po ustawieni szeregu flag informujących o sposobie
wywołania procesu oraz jego reprezentacji graficznej uruchamiana jest kluczowa metoda
start(), która zapoczątkowuje wykonywanie algorytmu.
Oczekiwanie na wyniki zostało zaimplementowane jako Coroutine, w której po jednej
sekundzie uruchamiana jest metoda czekajNaSarmate(). Jeśli spełnione są odpowiednie
warunki, tj. interfejs Sarmaty został uruchomiony poprawnie, sczytywane są wszystkie
dane wyświetlone na jego wyjściu standardowym. Na podstawie tych informacji
podejmowana jest decyzja i ustawiany odpowiedni typ i kształt czaru. Realizuje to metoda
podejmijDecyzje, która jest zbiorem instrukcji warunkowych.
4.4 Opis elementów
4.4.1. Gracz
Model postaci jest reprezentacją wirtualnego awatara gracza. Poprzez odpowiednio
skonfigurowane interfejsy człowiek uzyskuje możliwość jej kontroli w pewnych
zakresach. Postacią gracza jest czarodziej pozyskany z zasobów systemu Mixamo
należącego do Adobe.
str. 20
Gracz na scenie jest reprezentowany przez obiekt klasy GameObject. Zawiera on
podstawowe algorytmy, pozwalające na poruszanie się w trójwymiarowym świecie.
Posiada on szereg komponentów, które zostały zastosowane również w innych elementach
gry. Podstawowym komponentem jest Transform, przechowuje on współrzędne gracza
oraz posiada metody pozwalające na przemieszczanie obiektu w przestrzeni. Ważnym i
również wykorzystywany w prawie każdym ruchomym obiekcie sceny komponentem jest
instancja RigidBody. Jest to kolejna klasa Unity która odpowiada za podstawy fizyki.
RigidBody posiada metody odpowiedzialne za dodawanie siły jak również wykrywanie
kolizji. Klasy te są dokładnie opisane w dokumentacji Unity3D. Do obiektu załączone są
również autorskie klasy odpowiadające za ruch oraz strzelanie.
Klasa ruch jest odpowiedzialna za poruszanie się postaci. Za pomocą klasy Input
reprezentującej wbudowany menadżer wejścia ze środowiska Unity3D, zrealizowane jest
pobieranie przycisków z klawiatury, które następnie przetwarzanie są na pożądany
kierunek ruchu postaci. Sterowanie odbywa się poprzez wciskanie klawiszy WSAD.
Input Manager interpretuje wciśniecie klawisza przekładając go odpowiednio na dodatnie
lub ujemne wartości z przedziału [-1:1] w zależności od ustawień kierunków osi np. oś
horyzontalna ma przypisane klawisze A oraz D gdzie wciśnięcie A zmniejsza wartość, a
wciśniecie D zwiększa, poruszanie się po wartościach zmiennoprzecinkowych pomaga
uzyskać większą precyzje sterowania. Detekcja wciśnięcia następuje dzięki metodzie
GetKey z klasy Input, która jako argument może otrzymać zarówno string odpowiadający
danemu klawiszowi jak i odpowiedni typ wyliczeniowy. Jednak samo uzyskanie wartości
w celu określenia kierunku ruchu obywa się przy pomocy metody GetAxis z klasy Input
która jako argument przyjmuje string z określeniem wczytywanej płaszczyzny. W tym
przypadku są to osie „Horizontal” dla ruchu na boki oraz „Vertical” dla ruchu do przodu i
do tyłu. Gracz porusza się wzdłuż osi X oraz Z. Za pomocą myszki uzyskuje się
możliwość obracania postaci wokół osi Y. Realizacją tej operacji w kodzie zajmuje się
również metoda GetAxis przyjmując jako argument „Mouse X”. Wartości wejścia
sprawdzane są w funkcji Update() czyli co każdą kolejną klatkę.
Rysunek 7 Gracz
str. 21
Wartości zmiennoprzecinkowe pobierane z osi poziomej i pionowej są również używane
do łączenia animacji w wbudowanym w Unity narzędziu do łączenia animacji. Sam
animator gracza jest również bardzo złożony. Animacje ruchu są dostarczane z BlendTree,
jednak samo narzędzie działa tylko w momencie ustawienia zmiennej „move” poprzez
naciśnięcia przycisku ruchu z poziomu klasy odpowiedzialnej za ruch. Animator gracza
jest dwuwarstwowy. Na jednej warstwie odbywają się animacje ruchu, a na drugiej
uruchamiane jest każdorazowo animacja rzucania zaklęcia. Animacje pomiędzy
warstwami są łączone za pomocą suwaków w edytorze. Pozwala to uzyskać w pełni
animowaną postać korzystając jedynie z pojedynczych animacji będących reprezentacją
jednej czynności.
Rysunek 8 BlendTree odpowiedzialne za animacje ruchu gracza
Klasa odpowiedzialna za rzucanie zaklęć jest podobnie jak ruch klasą behawioralną opartą
o metodę Update(), a więc wykonującą się co klatkę. Sprawdzane jest wciśnięcie lewego
przycisku myszki, a kiedy ono nastąpi wywoływana jest metoda quasi-współbieżna
shootingEvent()[19]
uruchamiająca procedurę strzału. Metoda ta ustawia zmienna w
animatorze wybierając jedną z dwóch dostępnych animacji strzału i czeka 0.6s, aż
animacja postaci dotrze do miejsca kiedy ręce czarodzieja są gotowe do wystrzału, wtedy
wywoływana jest metoda shoot() odpowiedzialna za instancjonowanie pocisku.
Instancjonowanie pocisku gracza odbywa się za pomocą wbudowanej w edytor metody
Instantiate()[18]
służącej do instancjonowania obiektów. Za pomocą tej metody tworzony
jest obiekt z uprzednio przygotowanego elementu pocisku. Sam pocisk został opisany w
podrozdziale czary.
str. 22
W metodzie Update() klasy odpowiedzialnej za strzelanie znajduję się również kod
odpowiedzialny za zaznaczanie celów dla Chowańca - minotaura. Zaznaczenie celu
odbywa się poprzez wypuszczenie promienia równoległego do kierunku wzroku maga,
zaznaczony zostaje więc cel którego komponent Collider zostanie przecięty promieniem.
Promień jest instancjonowany metodą Instantiate(), gdy cel zostanie zaznaczony referencja
do jego obiektu na scenie zostanie przekazana do minotaura, który będzie oczekiwał na
rozkaz ataku.
4.4.2. GameMaster
Założenia gry przedstawionej w pracy inżynierskiej opierają się na kolejnych falach
atakujących potworów. Realizacja rozwiązania tego problemu odbywa się za pomocą tzw.
„spawnera” czyli obiekt typu GameObject. Element mimo tego, że znajduje się na scenie
nie jest widzialny dla gracza. Odpowiada za utrzymywanie stałej liczby przeciwników
zależnej od aktualnego poziomu.
Po stworzeniu obiektu klasy GameMaster na scenie pojawia się zadana ilość potworów.
Można ją kontrolować poprzez podanie jako argument komponentu. W każdej klatce gry
sprawdzana jest ilość przeciwników danego typu. Jeśli jest mniejsza niż maksymalna
tworzony jest nowy wróg i umieszczany na planszy. Pozycja nowego przeciwnika jest
losowana. Podczas uzupełniania puli, jeśli nowa pozycja znajduje się zbyt blisko punktu
docelowego, następuje przesunięcie o wektor o długości zadanej jako minimalna
odległość.
GameMaster odpowiada jednak nie tylko za tworzenie określonej liczby wrogów.
Przechowuje również podstawowe informacje o aktualnym postępie gracza wynik
punktowy oraz aktualny poziom. Gracz zdobywa punkty zabijając wrogów. Jako, że
podstawowym celem gry jest utrzymanie się jak najdłużej przy życiu realizacja tego celu
jest premiowana. Co minutę wzrasta poziom, a wraz z nim wzrasta ilość otrzymywanych
przez gracza punktów. Podstawowe wartości punktowe otrzymywane za każdego z
wrogów są mnożone przez aktualny poziom. Gra z czasem staje się jednak coraz
trudniejsza z każdą minutą wzrastają maksymalne progi zadanej ilości potworów. Z
każdym poziomem w grze przybywa po jednym barbarzyńcy i mutancie oraz co drugi
poziom jednym demonie.
Klasa GameMaster zawiera również metodę odpowiadającą za resetowanie gry metoda ta
wyszukuje wszystkich wrogów w grze i zabija ich, wyszukuje również gracza którego
przestawia w miejsce startu przywracając mu podstawowe wartości życia oraz resetuje
poziom i wynik punktowy. Metoda ta znajduje zastosowanie w menu głównym przy
starcie nowej gry.
4.4.3. Capzone
str. 23
Capzone to kluczowy z punktu widzenia gracza obszar na mapie. Głównym zadaniem
maga jest obrona strefy od której zależy jego życie. Strefa znajduję się w centralnej części
mapy i jest również miejsce w którym gracz rozpoczyna swoje zmagania. Jej graficzną
reprezentacją jest fioletowa półsfera stworzona jako efekt cząsteczkowy dzięki czemu
wygląda jakby była animowanym polem siłowym zgodnie z założeniem gry. Zadaniem
zarówno barbarzyńców jak i mutantów jest dotarcie do strefy, gdzie są one niszczone przez
pole siłowe, ale w wyniku zderzenie z nim graczowi odbierana jest cząstka życia. Z punktu
widzenia kodu odbywa się to w klasie Destroyer przypisanej jako komponent do obiektu
Capzone znajdującego się na scenie. Klasa Destroyer zawiera referencje do obiektu gracza.
Referencja do obiektu gracza jest przypisana w celu czysto wydajnościowym. Zamiast
przechowywać referencje można w razie konieczności wyszukać obiekt za pomocą funkcji
FindObjectOfType<>() jednak ta funkcja powoduje spadki wydajności co w przypadku
grafiki 3D jest bardzo niekorzystne zwłaszcza jeżeli ta funkcja miałaby być wywołana przy
każdym kontakcie wroga z półsferą. Obiekt Capzone zawiera komponent Collider w
kształcie sfery, połowa z niej znajduję się jednak pod powierzchnią terenu, jest więc
pomijalna, Istotna pół sfera znajduję się nad powierzchnią i pokrywa się z graficzną
reprezentacją pola siłowego. Komponent ma ustawioną flagę isTrigger, oznacza to, że z
punktu widzenia fizyki obiekt jest przeźroczysty. Collider obiektu Capzone jest związany z
klasą Destroyer która posiada metodę edytora Unity - OnTriggerEnter(). Mimo
transparentności kolizje obiektu z obiektami kolidujących warstw są rejestrowane. Jeżeli
obiekt wchodzący w strefę, pochodzi z warstwy reagującej z warstwą obiektu Capzone ( co
zależy od ustawień fizyki w Unity przedstawiony na poniższym diagramie), metoda jest
wywoływana.
Capzone znajduje się na warstwie Capzone, a wrogowie na warstwie Enemy – jak widać
zachodzi między nimi reakcja, a więc za każdym razem gdy wróg wejdzie w strefę
uruchamiany jest kod znajdująca się w ciele metody. Sprawdzane jest słowo kluczowe
przypisane do obiektu wchodzącego w kolizje, jeżeli jest nim "Enemy" na obiekcie gracza
przez referencje wyszukiwany jest (za pomocą GetComponent<>()) komponent LifeTime
str. 24
przypisany do gracza, a wtedy wywoływana jest na nim metoda Hit() z argumentem
równym obrażeniom zadawanym graczowi. Z punktu widzenia demo jest to 10 docelowo
jednak wartość ta może być zmieniona na przykład przypisana do każdego z wrogów.
Następnie GameObject wroga jest niszczony, co jest równoznaczne ze zniszczeniem
całego obiektu.
Poza graficzną reprezentacją pola siłowego Capzone posiada jeszcze dwa dodatkowe
efekty cząsteczkowe, słup światła opadający na środek strefy oraz drobne cząsteczki
emitowane ze szczytu kopuły. Efekty te mają na celu umożliwienie zlokalizowania strefy
przez gracza z dużej odległości w sytuacji gdy się od niego oddali.
Capzone jest elementem skończonym możliwym do przeniesienie i zaadaptowania na
każdej możliwej arenie walki. Jest on także dobrym przykładem wykorzystania emulacji
fizyki zawartej w Unity3D.
4.4.4. Magia
Podstawowym narzędziem walki każdego czarodzieje jest magia. Gracz w walce z
przeciwnikami posiada szeroki zasób zaklęć zmienianych jak zostało wspomniane
wcześniej poprzez interfejs głosowy. Wystrzeliwaniem magicznych pocisków zajmuje się
opisany w dziale gracz komponent odpowiedzialny za strzelanie. Przechowuje on także
referencje do instancjonowanego wzorca pocisku który ustawiany jest za pomocą innego
komponentu. Tym komponentem jest klasa zmienCzar. W tej klasie znajdują się dwa
obiekty typów wyliczeniowych, zawierające odpowiednio typ czaru oraz jego żywioł.
Klasa zawiera również listę przygotowanych wzorców poszczególnych pocisków
magicznych zmienianych, wybór i instancjonowanie odpowiedniego wzorca odbywa się
poprzez zawartą w klasie metody. Zmienna używanego zaklęcia jest inicjowana poprzez
użycie klienta rozpoznawania mowy. Po poprawnym rozpoznaniu wywoływana jest
metoda zmieniająca czar na podstawie instrukcji warunkowych co zostało opisane przy
okazji opisu implementacji sarmaty. Po stworzeniu wybranego wzorca wykonywana
ustawiany jest licznik dostępnych użyć czarów, a także wywoływana jest kolejna z metod.
Metoda ta przyjmuje jako argument GameObject reprezentujący instancjonowany czar.
Służy ona przeładowaniu czaru, niszczy stary wzorzec, przypisuje nową instancje do
hierarchii gracza, ustawiając Transform gracza rodzicem zaklęcia. Deaktywuje również
obiekt (aby zatrzymać wykonywanie akcji na wzorcu), nadpisuje też referencje wzorca.
Poniższa tabela przedstawia dostępne zaklęcia – ich nazwy są równoznaczne z
koniecznymi do wypowiedzenia słowami.
str. 25
Nazwa: Kula Magii Kula Ognia Wybuch Ognia Kula Lodu Wybuch Magii
Obrażenia: 20 20 50 30 50
Zasięg: 1 1 12 1 7
Ilość użyć: 7 6 3 5 4
W tabeli przedstawiono nazwy, a także poszczególne szczególne parametry każdego z
czarów takie jak obrażenia zadawane wrogom. Zasięg jest istotny tylko w przypadku
zaklęć obszarowych, jedynka oznacza, że zaklęcia atakuje tylko pojedynczy cel. Ilość użyć
to ilość możliwych do oddania strzałów od zmiany zaklęcia. Wprowadzenie takiego
mechanizmu ma na celu wymuszenie na graczu zmianę, a co z tym idzie skorzystanie z
interfejsu głosowego.
Każdy z dostępnych czarów jest przygotowanym według określonego schematu obiektem.
Indywidualne parametry zaklęć są ustawione ręcznie w panelu inspektora. Obiekty
zawierają również specjalne komponenty wspólne dla wszystkich zaklęć. Kluczową klasą
jest klasa Ammo zawierającą takie zmienne jak zadawane obrażenia, czas życia oraz flaga
dwustanowa która służy oznaczeniu czy zaklęcie jest obszarowe czy atakuje pojedynczy
cel. Klasa Ammo poza metodą Update() opisywaną wielokrotnie posiada jeszcze dwie
wbudowane metody edytora Unity. Pierwsza z nich to OnTriggerEnter() jest ona
wywoływana w momencie kolizji dowolnego obiektu z obiektem posiadającym
komponent z tą metodą. Jako argument przyjmuje ona obiekt klasy Collider będący
komponentem obiektu wchodzącego w kolizje z obiektem na którym wywoływana jest
metoda. Kolizje są rozpoznawane pomiędzy obiektami w zależności od ustawień fizyki w
edytorze. Unity pozwala na ustawienia warstw pomiędzy którymi mają zachodzić kolizje.
Pociski wystrzeliwane przez maga mają ustawione w komponencie Collider flagę isTrigger
oznacza tą, że z punktu widzenia fizyki są one przeźroczyste, natomiast sam komponent
pozwala na wykrycie przenikania się obiektów fizycznych. Metoda OnTriggerEnter() ta w
momencie wywołania instancjonuje efekt cząsteczkowy będący oznaka reakcji pocisku na
kolizje, a także za pomocą metody GetComponent edytora Unity wyszukuje na
wywołującym metodę OnTriggerEnter() obiekcie czyli wrogu komponent LifeTime na
którym wywołuje metodę Hit opisana w dziale wrogowie. Metoda ta za argument
przyjmuje obrażenia mające być zadane wrogowi przez zaklęcie. Jeżeli jednak pocisk jest
obszarowy obrażenia zadawane w tym momencie są równe zero, ponieważ są one
zadawane w kolejnym kroku wszystkim wrogom w zasięgu. Niszczony jest także pocisk.
Drugą metodą jest OnDestroy() metoda ta jest wywoływana w momencie rozpoczęcia się
niszczenia obiektu, a jest faktycznym zniszczeniem. Jest ona używana do zadawania
obrażeń w przypadku zaklęć obszarowych. Ich wzorce posiadają jednak dodatkowy
komponent, klasę odpowiedzialna za znalezienie wrogów w obszarze rażenia. Klasa
znajduję wszystkich wrogów na scenie, następnie sprawdza czy ich odległość od danego
punktu jest mniejsza od zadanego promienia, jeżeli tak jest są oni dodawani do listy
str. 26
wykorzystywanej między innymi przez amunicje. W tym przypadku metoda Hit() jest
wywoływana na wszystkich wrograch znajdujących się w zasięgu.
Jako grafiki pocisków wykorzystywane są efekty cząsteczkowe, pobrane z AssetStore.
Efekty cząsteczkowe są dobrą reprezentacją żywiołów, ze względu na ich płynną
wbudowaną animacje. Emisja niewielkich cząsteczek może emitować ogień czy śnieg.
Gotowe, pobrane ze sklepu Unity efekty są złożeniem kilku odpowiednio połączonych
emiterów cząsteczek jak również odpowiedniej operacji światłem.
Magia jest jednym z kluczowych elementów gry, dlatego działanie czarów, różnice między
nimi, oraz ich wygląd wymagają bardzo dużego nakładu pracy. Komponenty oraz
hierarchia wzorców poszczególnych pocisków są przygotowane w taki sposób, aby
dodawanie kolejnych nowych zaklęć było łatwe i odbywało się poprzez duplikowanie
gotowych wzorców, zmianę parametrów, efektu cząsteczkowego oraz oświetlenia.
Pozwala to na szybką produkcję zawartości i rozwój gry w kierunku zamkniętego
produktu.
4.4.5. Minotaur
Koncepcja minotaura została wprowadzona w celu poszerzenia funkcjonalności gry o
nowe elementy sterowania głosowego. Sterowany jest on za pomocą komendy głosowej
będącej alternatywą do sposobu rzucania czarów. Po poprawnym wykryciu polecenia
ataku i uprzednim oznaczeniu celu, minotaur rusza w kierunku przeciwnika. Po
znalezieniu się w zasięgu ataku cel zaczyna tracić punkty życia. W standardowym trybie
minotaur podąża za graczem czekając na jego rozkaz. Model minotaura został pobrany z
Asset Store Unity wraz z kompletem animacji.
Klasa opisująca logikę minotaura nazwana została zachowanieMinotaura. Zawiera metody
odpowiedzialne za podążanie za celem. Główną metodą, wywoływaną co klatkę jest
update. Zarządza ona stanami zbioru flag, odlicza czas potrzebny do wywoływania
animacji czy zadawania obrażeń oraz po uprzednim przygotowaniu wyżej opisanych
elementów, wywołuje metody z właściwą funkcjonalnością.
Pierwsza z nich to follow - metoda typu void, nie przyjmująca żadnych argumentów,
odpowiedzialna za przeprowadzenie odpowiednich akcji na aktualnym celu. Jeśli została
wydana komenda, ustawiona jest flaga mówiąca czy ostatni atak został przeprowadzony w
zadanym okresie czasu oraz minotaur znajduje się wystarczająco blisko celu wykonywany
jest cios. Polega on na zdaniu obrażeń celowi, wylosowaniu jednej z trzech dostępnych
animacji oraz jej odtworzeniu.
W przypadku gdy minotaur znajduje się zbyt daleko od swojego celu ruszy w jego
kierunku po prostej przecinającej jego położenie oraz położenie przeciwnika. Jeśli
minotaur w danym momencie nie zadaje ciosów może wejść w tryb podążania lub
spoczynku. W każdym z nich odgrywana jest odpowiednia animacja.
str. 27
Klasa posiada również dwie metody pomocnicze – shouldFollow, która nie przyjmuje
żadnych argumentów i decyduje czy minotaur jest w stanie spoczynku, czy podąża za
celem oraz posToRadius, czyli metodę która oblicza odległość w przestrzeni
dwuwymiarowej minotaura od przekazanych w argumentach współrzędnych.
4.4.6 Przeciwnicy
Zadaniem przeciwników jest dotarcie do obszaru, którego broni gracz. Za utrzymanie ich
liczby jest odpowiedzialny spawner. Ich modele wraz z animacjami pozyskano z systemu
Mixamo firmy Adobe. Zostały wyróżnione trzy odmienne typy wrogów, których
podzielono na dwie klasy w zależności od pełnionej przez siebie roli. Pierwsza z nich
opiera się na zdolności samodzielnego ataku na broniony przez gracza obszar. Przeciwnicy
tej kategorii są wytrzymali, ale poruszają się w przewidywalny, liniowy sposób.
Każdorazowo po osiągnięciu przez przeciwnika celu gracz traci ustaloną ilość punktów
życia. Drugim typem jest kategoria wspomagająca, która dzięki nieliniowemu sposobowi
poruszania oraz zaklęciom wspomagającym jego towarzyszy staje się trudnym do
pokonania oponentem. Postacie wspomagające nie mają możliwości przejścia do
bronionego obszaru, jedynie zbliżają do jego granic swoją pozycje. Każdy z przeciwników
poza klasą odpowiadającą za ich zachowanie zawiera również komponent klasy LifeTime
będący reprezentacją cyklu życia wszystkich obiektów możliwych do zniszczenia.
Instancję klasy LifeTime zawiera również obiekt gracza, ponieważ gracz również posiada
punkty życia i otrzymuje obrażenia za pomocą wywoływania tych samych metod, jednakże
przez klasę capzone, a nie jak w przypadku wrogów przez kolizje z amunicją. Klasa ta
przechowuje parametry związane z życiem obiektów możliwych do zniszczenia. Zawiera
również referencje do paska życia czy animatora obiektu macierzystego. Referencje te są
konieczne, ponieważ obiekty do których się odnoszą są wykorzystywane w metodach
znajdujących się w klasie. W animatorze ustawiana jest wartość flagi „Hit” mającej w
przypadku gdy postać posiada animację obrazującą reakcje na otrzymane obrażenia
odtworzyć ją. Pasek życia gracza jest widoczny na interfejsie jest również uaktualniany
instancję tej klasy będącą komponentem gracza. Takie rozwiązanie pozwoli w przyszłości
na dodatnie pasków życia również wrogom. Klasa LifeTime posiada dwie autorskie
metody oraz dwie standardowe opisywane wielokrotnie Start() i Update(). Pierwszą
metodą jest Kill() wywoływana automatycznie w Update() kiedy spełniony jest
odpowiedni warunek. Jest nim sprawdzenie punktów życia obiektu, jeżeli ich liczba jest
mniejsza niż zero jednostka jest zabijana. Zabicie odbywa się w następującej kolejności
ustawiana jest flaga oznaczająca że obiekt jest martwy, tworzony jest efekt cząsteczkowy
będący oznaką śmierci jednostki, następnie w przypadku wrogów dodawana jest do
wyniku odpowiednia dla każdego z nich wartość punktowa przemnożona przez aktualny
poziom, a sam obiekt zostaje przeznaczony do zniszczenia. Aby jednak punkty życia
zostały zredukowane konieczna jest metoda odejmująca je. Tą metodą jest Hit()
wywoływana każdorazowo przez klasę Ammo w wypadku kolizji pocisku gracza z
komponentem Collider wroga bądź w razie konieczności gdy wróg znajduje się w obszarze
rażenia pocisku obszarowego.
str. 28
Barbarzyńca i mutant
Są to postacie czysto ofensywne, których jedynym celem jest bieg na przód. Barbarzyńca
jest dużo mniej wytrzymały względem mutanta, za jego zabicie graczowi przyznawane jest
tylko 10 punktów (mutant dostarcza ich aż 20), jednak występuje trzykrotnie częściej. Po
stworzeniu przez spawner postaci, automatycznie ustawiana jest ona przodem do punktu
docelowego, do którego biegnie w linii prostej.
str. 29
str. 30
Demon
Przeciwnik demon powstał z myślą o postaci wspomagającej, która nie brała by czynnego
udziału w walce, czy utracie przez gracza punktów życia. Wspiera on swoich sojuszników
za pomocą magii oraz unika pozostawania zbyt długo w jednym miejscu, poruszając się
nie liniowo. Może rzucić on obszarowe zaklęcie, zwiększając szybkość każdego
barbarzyńcy w zadanym promieniu.
O czasie trwania przyspieszenia decyduje publiczna zmienna buffTime, która standardowo
ustawiona jest na 1 sekundę, zaś o promieniu publiczna zmienna typu float zasięg
znajdująca się w klasie pomocniczej znajdzObszar, ustawiona na 10. Kluczowa jest tutaj
metoda randAndCheckTimeToTP, przyjmująca jako argumenty początek oraz koniec
przedziału, z którego będą losowane liczby, odpowiadające za czas pozostania demona w
określonym miejscu. Gdy ciągle dekrementujący się licznik osiągnie wartość zero oraz
uprzednio został wylosowany czas (który właśnie upłynął) metoda zwróci flagę,
informującą algorytm iż po zakończeniu aktualnej akcji należy wykonać operację
teleportacji. Za natychmiastowe przemieszczenie odpowiedzialna jest metoda nie
zwracająca typu performTP. Losuje ona nową pozycję tak długo, aż znajdzie taką, która
jest bliżej punktu bronionego przez gracza niż aktualna. Wykonywana jest wtedy animacja
teleportacji oraz samo przemieszczenie modelu. Po jej zakończeniu, odtwarzana jest druga
część animacji, która kończy proces. Algorytm, jeśli uzna iż w danym momencie nie
odbywa się inna akcja, będzie ciągle rzucał czar wspomagający.
str. 31
W klasie znajdzObszar zawarta jest funkcjonalność wyszukiwania wrogów w danym
obszarze. Każdy z oponentów oznaczony jest specjalnym słowem kluczowym „Enemy”.
Klasa ta, od momentu stworzenia co klatkę odświeża i przechowuje listę wszystkich
dostępnych postaci z tej kategorii. Jeśli zostanie wywołana metoda znajdzWrogow, która
w argumencie otrzymuje promień wyszukiwania, którego centrum jest obiekt ją
wywołujący – lista wrogów zostanie przejrzana, sprawdzając kolejno czy ich położenie
spełnia założenia. Jeśli tak, zostają dodani do specjalnej publicznej listy o nazwie
wrogowieZasieg. Używając referencji do niej, inne klasy mogą wykonywać operacje na
wszystkich przeciwnikach w niej zawartych, np. zwiększając ich szybkość.
4.4.7. Interfejs użytkownika.
Interfejs użytkownika jest wzorowany na standardzie stosowanym w wielu grach
komputerowych. Z względu na fakt, że jest to demo gry mające na celu prezentacje
określonego elementu, a więc zastosowanie rozpoznawania mowy w grze to menu
zarówno menu główne jak i menu pauzy są stworzone z podstawowych elementów
dostępnych w edytorze. Tworzenie interfejsów użytkownika z punktu widzenia
oprogramowania w Unity3d jest bardzo proste ze względu na fakt iż każdemu elementowi
interfejsu odpowiadają klasy takie jak „Button” czy „Image”. Jednak jest również bardzo
czasochłonne, ponieważ wymaga umiejętności projektowania grafiki oraz zdolności
artystycznych. Dostępne w Internecie gotowe paczki z elementami graficznego interfejsu
użytkownika są zazwyczaj nieużyteczne i nie pasujące do stylistyki samej gry. Zawodowi
deweloperzy korzystają z usług grafików którzy tworzą projekty menu na zamówienie. Ze
względu na brak grafiki i bardziej programistyczne nastawienie pracy posłużono się
szczątkowymi grafikami.
str. 32
Zarówno menu główne jak i menu pauzy są stworzone w oparciu o obiekt typu Canvas,
jest to wirtualne płótno na którym umieszczane są wszystkie elementy mające być
wyświetlone na wierzchniej warstwie obrazu. W przypadku menu zastosowano ustawienie
pozwalające na wyświetlenie płótna bezpośrednio na pierwszym planie kamery
zostawiając resztę sceny pod spodem. Elementy są umieszczane jako obiekty klas
pochodzących z biblioteki UI edytora Unity. Komponenty te mogą być interaktywne i być
automatycznie połączone z instancją EventSystem odpowiedzialną za śledzenie
wykonywanych przez gracza akcji. Menu składają się z przycisków (obiekty klasy Button),
obrazów (klasa Image), oraz tekstu (klasa Text), są rozmieszczone w sposób czytelny i
funkcjonalny, co przedstawiono na poniższych zrzutach ekranu. Wszystkie elementy UI
znajdujące się na płótnie zamiast zwykłej instancji Transfrom zawierają RectTransfrom
jest to specjalna klasa pozwalająca na dobranie takich parametrów jak rozmiar okna
zawierającego komponenty danego obiektu jak również pozwala na zakotwiczenie obiektu,
co z kolei daje możliwość tworzenie odpornego na zmiany rozdzielczości interfejsu
użytkownika. Wszystkie panele interfejsu posiadają referencje do klasy
MainMenu stworzonej w celu zarządzania poszczególnymi panelami menu, oraz
przejściami pomiędzy nimi. Klasa MainMenu posiada również referencje do obiektów
wspólnych dla poszczególnych paneli takich jak tło czy zacienienie.
Na ilustracji menu głównego widzimy panel przycisków oraz tytuł. Tytuł nie jest
oficjalnym tytułem demo gry, jest to tylko tymczasowy tekst napisany, aby możliwe było
zakotwiczenie obiektu typu Text na scenie, oraz dobranie odpowiedniego rozmiaru i
Rysunek 9 - Screen menu głównego[20]
str. 33
koloru czcionki. Obiekt ten został związany z górną krawędzią płótna (obejmującego
obszar wielkości zgodnej z rozdzielczością ekranu), dzięki temu tekst pozostanie w górnej
części ekranu w każdej rozdzielczości. Tło to grafika JPG uzyskana z darmowych zasobów
dostępnych w Internecie, przetworzona za pomocą Unity w obiekt typu Sprite i
umieszczona w komponencie typu Image na wirtualnym płótnie (Canvas). Przyciski to
wspominane wcześniej obiekty z komponentami klasy Button, w swojej hierarchii
zawierają one również instancje klasy Text dzięki czemu możliwe jest umieszczenie na
nich tekstu informującego do czego służy dany przycisk. Przyciski posiadają wbudowaną
metodę OnClick śledzoną przez EventSystem. W inspektorze możemy przypisać do niej
dowolną akcje. Najczęściej jest to jednak wywołanie odpowiedniej metody z klasy
odpowiedniego panelu. Przycisk rozpocznij grę wywołuje przy naciśnięciu metodę
StartGameButton() z klasy MainPanel. Metoda ta restartuje grę i dezaktywuje elementy
interfejsu związane z menu, uruchamiając te związane z bieżącą sesją gracza (pasek
zdrowia czy punkty). Metoda wyłącza także pauzę aktywną kiedy menu jest na ekranie.
Przycisk instrukcje ukrywa panel menu uruchamiając panel instrukcji. Podobnie jak
przycisk wyjdź z gry który uruchamia panel QuitGame i powoduje zniknięcie tytułu.
Panel QuitGame posiada dwa przyciski, przycisk „Tak” wywoła metodę zamykającą grę,
natomiast przycisk „Nie” metodę GoToMainMenu z klasy MainMenu która zamknie
bieżący panel i otworzy panel MainPanel czyli menu główne, oraz obraz z tekstem
widocznym na powyższym zrzucie ekranu.
Panel instrukcji w jest półprzezroczystym panelem zawierającym krótki opis tego jak grać,
co ważne jest on dostępny zarówno z poziomu menu głównego jak i pauzy. W zależności
od miejsca wywołania przyjmuje tło które znajdowało się pod panelem poprzedzającym,
panel instrukcji posiada zmienną do której podczas otwierania go zapisywana jest
referencja do poprzedniego panelu. Wciśnięcie „Escape” powoduje powrót do miejsca
str. 34
zapisanego w zmiennej. Panel instrukcji w obu wersjach przedstawia poniższe zrzuty
ekranu.
Poniższy zrzut ekranu przedstawia menu pauzy uruchomionej w trakcie gry za pomocą
wciśnięcia klawisza „Escape”. Ze względu na to, iż wszystkie bieżące akcje w projekcie
są wykonywane w Update(), a więc w kolejnych klatkach co oznacza zależność od czasu
pauza jest realizowana poprzez ustawienie skali czasu na zero. Unity dostarcza klasę Time
pozwalająca na zarządzanie upływem globalnego czasu w grze. Po ustawieniu wartości
timeScale statycznego parametru klasy Time na zero czas w grze zostaje zatrzymany. W
tym momencie w kolejnych klatkach będą odbywać się tylko akcje które zostały
str. 35
skonfigurowane jako niezależne od czasu. Ze względu na brak konieczności wykonywania
się na przykład animacji w trakcie pauzy, zastosowanie tej metody pauzowania gry nie
sprawia żadnych trudności.
W menu pauzy jest widoczny pasek zdrowia gracza, pozostałe elementu interfejsu gracza
są chowane ze względy na czytelność menu. Wraz z panelem uruchamiany jest także
obiekt „Shadow” posiadający półprzezroczysty komponent Image z czarnym obrazem
znajdujący się pod elementami panelu, a nad grą pozwalający na uzyskanie lepszego
kontrastu pomiędzy światem gry, a menu. Naciśniecie przycisków spowoduje wywołanie
odpowiednich metod. Menu pauzy opiera się o identyczne przyciski co menu główne,
zmienione zostały tylko napisy oraz funkcjonalności przypisane do metody OnClick(). W
przypadku przycisku „Instrukcje” jest to ta sama metoda. „Wróć do gry” powoduje
zamknięcie panelu pauzy i ustawienie parametru timeScale na wartość jeden (czyli
domyślną). Wciśnięcie przycisku wyjdź do menu wywoła metodę GoToMainMenu() z
klasy MainMenu która zamknie menu pauzy i uruchomi menu główne.
Dzięki dostarczanej przez Unity w wersji wyższej niż 4.3 bibliotece UI realizacja interfejsu
staje się bardzo łatwa. Wcześniej każdy element umieszczało się przy pomocy
odpowiednio napisanego kodu. Elementy menu takie jak przyciski czy tekst przesuwało się
w odpowiednie miejsca przy pomocy wektorów, podobnie określało się wymiary okien czy
funkcjonalności. Nauczenie się korzystania z nowszej wersji interfejsu pozwoliło na
wprowadzenie do gry elementów interfejsu użytkownika korzystając z minimalnej ilości
kodu. Dodanie zarówno menu głównego jak i pauzy pozwalają graczowi poczuć się w
demonstracyjnym projekcie jak w zamkniętym produkcie.
str. 36
4.4.8 Kamera
Tworzona gra jest osadzona w koncepcji Third Person Perspective. Oznacza to, że kamera
umieszczona jest w taki sposób, aby gracz widział świat zza pleców sterowanej przez
siebie postaci.
Kamera w świecie gry jest gotowym komponentem dostarczanym w silniku Unity.
Komponent posiada wiele parametrów, które można dowolnie modyfikować, większość z
nich jest jednak nie istotna w tym projekcie dlatego ich wartości są domyślne. Wbudowana
kamera zapewnia parametr pozwalający na dobranie tła. Tło jest wirtualną sferą
obejmującą całość umieszonej na mapie sceny. Pozwala to na wypełnienie pola widzenia
kamery w którym nie znajduję się żadna grafika tłem. Element pełniący rolę wypełniacza
to Skybox. Jest to standard w grach o trójwymiarowej grafice. Stosowanie takiego
rozwiązania pozwala na uzyskanie wrażenia rzeczywistego nieba. Starsze gry
komputerowe posiadały obiekt nieba będący sześcianem stąd nazwa Skybox, obecnie
jednak przyjmuje się za najbardziej rzeczywiste odwzorowanie użycie sfery, dlatego też
zastosowane zostało właśnie takie rozwiązanie. Kolejnym istotnym parametrem kamery
jest jej pole widzenia manipulując polem widzenia możemy tworzyć efekty przybliżenia
czy oddalenia będące istotne na przykład przy tworzeniu trybu snajperskiego bądź trybów
kamery dostosowanych do różnych akcji. Sam komponent Camera nie zapewnia jednak
śledzenia gracza czy odpowiedniego ustawienia się kamery na scenie, odpowiada za to
autorska klasa CameraScript. Klasa ta zawiera bardzo wiele parametrów, większość z nich
odpowiada za różnego rodzaju przesunięcia kamery w poszczególnych stanach.
Kamera posiada dwa stany. Stan Normal oraz Aim przełączane prawym przyciskiem
myszki. Stan Aim jest przybliżeniem pomagającym w celowaniu. Celem stworzenia
wyjątkowego stanu była koncepcja, aby wprowadzić widoczny celownik zmieniając
również prędkość rotacji oraz precyzje celowania. Rozwiązanie jednak nie zostało
zrealizowane ze względu na problemu implementacyjne. Stan w kamerze pozostał,
ponieważ z punktu widzenia gracza strzelanie z tej perspektywy jest wygodniejsze,
jednakże sterowanie w tym stanie pozostaje bez zmian. Stan normalny pozwala na
spoglądanie na plecy gracza delikatnie z góry, obejmując również szeroki obszar terenu co
z kolei daje możliwość wypatrywania wrogów. Różnica między stanami polega na zmianie
parametru FieldOfView kamery, a także przesunięciu obiektu kamery poprzez manipulacje
parametrów komponentu Transfrom obiektu kamery. Klasa CameraScript zawiera także
referencje dwóch instancji klasy Transform obie znajdują się w hierarchii gracza. Jedna z
nich to środek postaci gracza, a drugi znajduję się przed graczem. Oba służą jako punkty
na które patrzy kamera, w zależności od aktualnego stanu. Po transformacji położenia
kamery i ustawaniu rotacji, wywoływana jest metoda wbudowana klasy Camera LookAt()
która jako argument przyjmuje instancje klasy Transfrom. Zmiana punktu na który patrzy
kamera w momencie zmiany stanu odbywa się w sposób płynny, ponieważ zastosowana
str. 37
jest metoda Lerp z klasy Mathf. Metoda ta pozwala na płynną zmianę parametru pomiędzy
dwoma z jej argumentów z szybkością podawaną jako trzeci argument.
Sterowanie kamery jest tylko odbywa sie tylko wokół osi Y wraz z obrotem postaci, w
miarę rozwoju projektu docelowo kamera powinna posiadać jeszcze stan Orbit
pozwalający na swobodny obrót kamery wokół własnej osi w momencie kiedy gracz
znajduje się w stanie spoczynku, całość powinna być także bardziej intuicyjna. W
projekcie postawiono jednak na implementacje własnych komponentów zamiast
kupowania gotowych dostarczanych przez AssetStore komponentów komercyjnych. W
trakcie dalszej implementacji projektu element ten prawdopodobnie uległby gruntownej
zmienia.
4.5 Sposób podziału pracy – Metodyka Agile
Jednym z podstawowych problemów zespołowego wytwarzania oprogramowania jest
synchronizacja zadań, które ma wykonać każdy z programistów. Odpowiedni podział
obowiązków, zadań i odpowiedzialności pozwala na tworzenie działającego kodu.
Temat zespołowego tworzenia oprogramowania jest poruszany od samego początku
istnienia układów programowalnych. Przeszedł wiele ewolucji i ciągle się rozwija.
Pierwszą koncepcją był model kaskadowy – nazwany po ang. waterfall. Jego idea opiera
się na wykonywaniu kolejnych czynności jedna po drugiej, przechodząc do następnej tylko
w przypadku, gdy poprzednia została ukończona i zaakceptowana. Koncepcja ta ma jednak
spore ograniczenia, nie pozwala elastycznie tworzyć kodu - praca wykonywana jest
szeregowo, błąd w dowolnej fazie projektu jest bardzo kosztowny i wymaga powtórzenia
całego etapu. Ze względu na swoje wady, które przekładały się wprost na koszty – firmy w
celu zmaksymalizowania zysków stworzyły kontr koncepcje programowania zwinnego –
ang. agile.
Metodyki zwinne pozwalają tworzyć kod równolegle, poprzez starannie podzielone
zadania oraz ciągły kontakt osób współtworzących projekt. W tej koncepcji od znalezienia
błędu do jego poprawienia mija bardzo mało czasu, gdyż znika ograniczenie oczekiwania
na kolejne etapy występujące np. w waterfallu[21]
. Jest to również doskonały wybór dla
zespołów z dynamicznymi wymaganiami względem kodu. Każda zmiana założeń projektu
nie zabiera dużo czasu.
W pracy zdecydowano się użyć metodyk zwinnych. Głównym argumentem
przemawiającym za tym właśnie sposobem była konieczność podziału pracy na dwie
osoby, które poświęcając niewielką ilość czasu na zadania biurokratyczne byłyby w stanie
niezależnie dążyć do zadanego celu. Naturalnym wyznacznikiem rytmu pracy były w tym
przypadku zajęcia projektowe z przedmiotu Gry Komputerowe, odbywające się raz w
tygodniu. Iteracja dla projektu zaczynała się więc w każdy piątek i trwała tydzień.
str. 38
Zastosowany model współpracy najbliższy jest scrumowi, który opiera się na codziennych
spotkaniach zespołu, gdzie każda z zaangażowanych osób zobowiązana była przedstawić
swój postęp prac, jak i wypowiedzieć się o aktualnych dla niej problemach.
Jeszcze podczas definiowania założeń pracy, przetestowano trzy sposoby gromadzenia
kodu w repozytorium. Pierwszym z nich było skorzystanie z oprogramowania Dropbox[22]
.
Pozwala on na współdzielenie poprzez sieć Internet katalogów. Jednak podczas
korzystania z Unity3D, zmieniane jest wiele plików tymczasowych – nie potrzebnych z
punktu widzenia współtworzenia kodu, które w tym podejściu były ciągle, niepotrzebnie
synchronizowane. Kolejną próbą było skorzystanie z GitHub’a[23]
, co również zostało
zaniechane z powodu konieczności synchronizowania dużej ilości modeli, plików
graficznych jak i ustawień samego Unity3D – dla którego GitHub nie proponował
odpowiedniego interfejsu. Rozwiązaniem tego problemu okazało się zastosowanie
wbudowanego w Microsoft Visual Studio rozwiązania Team Foundation, które dzięki
zastosowaniu odpowiedniego filtru, jak możliwości powiązania go z narzędziami do
organizacji metodyk zwinnych okazał się rozsądnym podejściem.
Stworzono także listę koniecznych do zrealizowania zadań, którym nadano tzw.
„estymatę” czyli liczbową wartość odpowiadającą ilości koniecznej pracy, by zakończyć
konkretne zadanie.
Aby zapewnić projektowi przejrzystość skorzystano z tablicy kanbanowej, czyli zbioru
zadań pogrupowanych według postępu prac nad nimi. Dostęp do takiego narzędzia
zapewnia wspomniane już wcześniej Microsoft Visual Studio Team Foundation. Już
podczas planowania projektu zdefiniowano większość zadań, które miały być zawarte w
jego ostatecznej formie.
Rysunek 10 Fragment tablicy Kanban
str. 39
Przed każdorazowym uaktualnieniem wspólnego kodu w systemie kontroli wersji, zmiana
była omawiana w obrębie zespołu oraz następnie zostały wprowadzane do niej poprawki
jeśli którykolwiek z członków projektu zgłosił zastrzeżenia.
4.6 Fazy projektowania
Praca została podzielona na wiele etapów. Pierwszym z nich było zdefiniowanie
początkowych założeń, które docelowo miały być spełnione lub zmienione po uprzednim
przedyskutowaniu. Podjęto próbę stworzenia gry opartej na rozgrywce dla wielu osób,
która dostarczała by wielu wrażeń użytkownikowi, pozwalając mu cieszyć się grą w
kooperacji lub pojedynku ze swoim towarzyszem. Plansza miała być odzwierciedleniem
krakowskiego rynku, jednak już po pierwszych próbach pracy w programie Blender
otrzymane wyniki były bardzo niesatysfakcjonujące ze względu na brak doświadczenia
zespołu w kwestii tworzenia profesjonalnej grafiki oraz brak koniecznych umiejętności
artystycznych, takich jak myślenie przestrzenne, dobór czy wytwarzanie tekstur.
Kolejnymi problemami były niska jakość animacji tworzonej na własnoręcznie robionych
modelach oraz czasochłonność procesu. Dlatego zdecydowano się na zaniechanie
tworzenia skomplikowanych modeli i skupiono się na ich doborze z gotowych źródeł jak
na przykład Asset Store. Takie podejście wymuszało stworzenie krajobrazu według
upodobań i wyobraźni zespołu, bez wzorowania się na jakimś konkretnym, istniejącym
miejscu.
Po zdobyciu modeli postaci wpasowujących się w założony na samym początku klimat
fantasy, zdecydowano się realizować mechanikę gry, odpowiadającą za rozmieszczenie i
logikę wrogów na planszy, warunki przegranej oraz możliwość odpierania kolejnych
napastników. W końcowej fazie tego projektu pojawiła się konieczność stworzenia
interfejsu dla systemu rozpoznawania mowy Sarmata, umożliwiającą spełnić
najważniejsze założenie – wprowadzić interfejs głosowy. Dzięki zastosowaniu metodyk
programowania zwinnego oraz dostępności odpowiednich zasobów postanowiono
prowadzić prace równolegle. Po stworzeniu działającego interfejsu i zintegrowaniu go z
grą oraz zakończeniu prac nad mechaniką przystąpiono do realizacji części ostatniej, przed
zakończeniem tworzenia nowej funkcjonalności – poprawieniu grafik, dodania szczegółów
i tła oraz innych obiektów nadających grze charakterystyczny klimat.
Tutaj również zespół połączył swoje siły, działanie w metodyce Agile pozwoliło na
równoległą pracę nad wykończeniem gry bez zbędnego blokowania się nawzajem, czyli
sytuacji gdy jeden programista uzależniony jest od pracy drugiego i musi czekać na jej
ukończenie.
Projekt nie był wolny od wad i dopiero w fazie testów, gdy prowadzono konsultacje z
graczami zgłoszono konieczność wprowadzenia kilku poprawek. Jedną z największych
wad wskazywanych przez graczy było połączenie sterowania z kamerą. Powstający w
trakcie biegania chaos został jednak zlikwidowany poprzez poprawienie stworzenie nowej
wersji skryptu kamery. Gracze wskazywali więcej drobnych wad, starano się je naprawić
str. 40
jak najlepiej, nie burząc gotowej już koncepcji i mechaniki gry jednocześnie rozwiązując
znaleziony problem. Według teorii testowania niemożliwe jest stworzenie przypadków
testowych i pokrycie nimi kodu w taki sposób, aby sto procent błędów zostało znalezione i
naprawione w taki sposób, który nie powoduje konieczności wprowadzania dalszych
zmian. Dzięki doświadczeniu zdobytemu podczas studiów, praktyk oraz w podjętej pracy
stwierdzono, iż niektóre z przypadków mimo tego że są potencjalnymi błędami nigdy nie
wystąpią, gdyż są na przykład blokowane przez inny element funkcjonalności kodu, które
potraktowano podobnie jak te z niską szkodliwością, praktycznie niezauważalnych –
zdecydowano się na pozostawienie je w kodzie bez żadnych poprawek, po uprzedniej
inwestygacji każdego przypadku.
5. Zakończenie
5.1 Podsumowanie
Implementacja gry komputerowej to złożone zajęcie, wymagające od zespołu
podejmującego się tej pracy dużej ilości zaangażowania oraz całego wachlarza
umiejętności. Osoby pracujące nad projektem powinny potrafić tworzyć zarówno grafikę
jak i modele trójwymiarowe, organizować pracę np. w metodykach zwinnych, posiadać
umiejętności z obszaru architektury jak i tworzenia oprogramowania oraz zbiór założeń,
które definiują kiedy praca jest uznana za gotową. Kluczowa jest również komunikacja
zarówno wewnątrz zespołu, jak i z osobą definiującą założenia. Wybór Unity jako
głównego silnika gry pozwolił na elastyczne tworzenie gry. Pomniejsze sposoby realizacji
ulegały ciągłej ewolucji, koncepcja zmieniała się zarówno po natrafieniu na barierę, jak i
znalezieniu prostszego lub bardziej eleganckiego rozwiązania. Projekt pochłonął bardzo
dużo czasu, który potrzebny był między innymi na tworzenie nowych rozwiązań,
wprowadzania poprawek do już istniejących czy ich wzajemne łączenie.
5.2. Problemy
Zespół podczas tworzenia pracy stanął przed wieloma wyzwaniami oraz natrafił na wiele
problemów. Głównym elementem sprawiającym problemy było połączenie modułu
interfejsu sarmaty z grą. Początkowe trudności ze stworzeniem działającej wersji napisanej
w języku C# spowodowane były brakiem równoważnych bibliotek użytych w
standardowej implementacji w C++. Został on rozwiązany po otrzymaniu od zespołu DSP
skompilowanej biblioteki dynamicznie linkowanej, z przeprowadzoną konwersją wersji
C++ do C# za pomocą CLI. Tak działający zbiór klas i metod potrzebnych do nawiązania
poprawnego połączenia z serwerem Sarmaty nie był jednak możliwy do użycia w
Unity3D. Pierwszym podejściem do rozwiązania tego problemu była próba włączenia dll
jako plugin natywny (czyli taki, który stworzony jest za pomocą innego języka niż
wspierany przez Unity3D C# oraz JavaScript). Zdecydowano się pominąć to rozwiązanie,
z powodu braku możliwości tworzenia instancji klas w nim zawartych, dostępne było
str. 41
jedynie używanie funkcji, co w tym przypadku nie rozwiązywało niczego. Podczas drugiej
próby, uzyskano bibliotekę skompilowaną z użyciem .Net w wersji 2.0. Niestety ta próba
również się nie powiodła, z powodu użycia części kodu skompilowanego w wersji 4.0,
ukrytego wewnątrz biblioteki. Ostatecznie zdecydowano się na stworzenie oddzielnego
programu odpowiedzialnego za utrzymanie sesji i wyświetlenie wyników.
Kolejnym problemem, jest spadek wydajności w klatkach, w których odbierana jest
informacja od interfejsu Sarmaty. Taka forma rozwiązania problemu jest jednak jedynie
tymczasowa, planowane jest ponowne stworzenie interfejsu.
Dużym kłopotem okazała się również implementacja kamery i celownika. Wzorując się na
profesjonalnych grach typu Assassins Creed firmy Ubisoft, próbowano uzyskać pływającą
kamerę, przełączaną w tryb celowania po użyciu odpowiedniego przycisku. Jednak
okazało się to zbyt trudne w realizacji i zdecydowano się na implementację prostszej
wersji, która jednocześnie pozwalała bezproblemowo poruszać się w świecie gry.
Gra działa bezproblemowo na komputerach z nowoczesnym i wydajnym sprzętem,
posiadających stałe łącze lub dostęp do sieci bezprzewodowej o wysokiej jakości. Wraz z
pogorszeniem się jakości łącza drastycznie rośnie czas odpowiedzi serwera Sarmaty,
głownie spowodowane koniecznością wysyłki dużego pliku zawierającego nagrany głos.
W ramach przygotowań do stworzenia właściwego projektu stworzono dwie inne gry, obie
zakończone fiaskiem. Zespół obeznał się wtedy z problemami dotyczącymi tworzenia
prostych gier, oraz sposobów pozyskiwania modeli z Internetu, jak i produkować własne.
Po kilku próbach tworzenia budynków i postaci w programie Blender, zdecydowano się na
korzystanie z gotowych już obiektów tego typu dostępnych w Internecie, z powodu niskiej
jakości własnych dzieł.
Główną rolę odgrywał również czas, terminy prezentacji kolejnych etapów pracy,
konieczność pogodzenia studiów, pracy zawodowej oraz życia osobistego z tworzeniem
zarówno gry, jak i jej opisu.
5.3. Przyszłość projektu
Projekt informatyczny, nigdy nie można uznać za zakończony. Wymaga on ciągłej troski i
wprowadzania kolejnych zmian. Wraz z czasem zmieniają się potrzeby klienta, pojawia się
nowa technologia pozwalająca na wydajniejszą implementację, czy pojawiają się nowe
pomysły na jego rozwój.
Kolejnym krokiem rozwoju projektu będzie reorganizacja interfejsu Sarmaty, w taki
sposób by działał jako niezależny serwer. Po wysłaniu pakietów na adres loopback
kierowałby dane do odpowiedniego serwera Sarmaty, a odpowiedź przebywałaby tą samą
drogę. Jest to o tyle wygodne w użyciu w połączeniu z grą w Unity3D, iż nie wymagało by
uruchamiania osobnego procesu, a jedynie utworzenia nowego obiektu na scenie
odpowiedzialnego za komunikację między serwerami.
str. 42
Większej dbałości wymagają również elementy grafiki – tutaj kluczową kwestią są
fundusze na zakup profesjonalnych grafik lub wynajęcie osoby, która współtworzyła by
grę pod tym kątem.
Istnieje możliwość rozbudowania rozgrywki o tryb dla wielu graczy. Pozwoliłoby to
umieścić drugą postać w świecie gry, której zadaniem była by obrona zadanego punktu,
lub współuczestniczenie w ataku. Będzie to dość pracochłonny projekt, ponieważ oprócz
stworzenia nowej mechaniki istnieje również potrzeba zbalansowania rozgrywki.
Zastosowany system czarów jest prototypem, wraz z pojawieniem sie informacji
zwrotnych od graczy zostaną opracowane nowe zaklęcia, które znacznie urozmaicą
rozgrywkę. Rozważane jest również wprowadzenie nowych map oraz przeciwników.
Aby wprowadzić powyższe zmiany w życie istnieje potrzeba uzyskania odpowiednich
zasobów w postaci funduszy lub osoby odpowiedzialnej za grafikę, oraz zespołu gotowego
poświęcić czas na jej dalszy rozwój.
Rysunek 1 Screen z gry Warcraft 3 obrazujący mapę typu Hero Defense[4]
......................... 7
Rysunek 2 Screen edytora Unity .......................................................................................... 11
Rysunek 3 Screen z gry z widocznym interfejsem .............................................................. 13
Rysunek 4 Screen z gry - Las ............................................................................................... 15
Rysunek 5 Screen z gry - Ciała niebieskie ........................................................................... 15
Rysunek 6 Screen z gry - wzgórze ....................................................................................... 16
Rysunek 7 Gracz .................................................................................................................. 20
Rysunek 8 BlendTree odpowiedzialne za animacje ruchu gracza ....................................... 21
Rysunek 9 - Screen menu głównego[20]
............................................................................... 32
Rysunek 10 Fragment tablicy Kanban ................................................................................. 38
str. 43
Bibliografia
1. http://dsp.agh.edu.pl/sarmata/
2. http://www.theesa.com/wp-content/uploads/2015/04/ESA-Essential-Facts-2015.pdf
3. https://en.wikipedia.org/wiki/List_of_best-selling_video_games#PC
4. http://www.hiveworkshop.com/forums/members/218181-albums5902-
picture64818.png
5. http://store.steampowered.com/app/336420
6. https://pl.wikipedia.org/wiki/Multiplayer_online_battle_arena
7. https://play.google.com/store/search?q=hero%20defense&c=apps
8. https://pl.wikipedia.org/wiki/Rozpoznawanie_mowy
9. http://www.slideshare.net/aerjotl/automatic-speech-recognition
10. http://www.gry-online.pl/S016.asp?ID=23688
11. http://store.steampowered.com/app/319740
12. http://store.steampowered.com/app/21800/
13. https://www.assetstore.unity3d.com/en/#!/content/38913
14. https://www.assetstore.unity3d.com/en/#!/content/21140
15. https://www.assetstore.unity3d.com/en/#!/content/22755
16. http://forum.unity3d.com/threads/119295-Writing-AudioListener.GetOutputData-
to-wav-problem?p=806734&viewfull=1#post806734
17. https://cafe.bevocal.com/docs/grammar/abnf.html
18. http://docs.unity3d.com/ScriptReference/Object.Instantiate.html
19. http://docs.unity3d.com/Manual/Coroutines.html
20. http://absfreepic.com/free-photos/download/the-full-moon-on-the-mountain-
3706x2622_70947.html
21. http://www.agilenutshell.com/agile_vs_waterfall
22. https://www.dropbox.com/
23. https://github.com/
24. https://unity3d.com/
25. https://www.visualstudio.com/en-us/products/visual-studio-team-services-vs.aspx
26. https://www.mixamo.com/