Unity3d FPS Tutorial, czyli tworzymy własną grę FPS od podstaw z wykorzystaniem silnika Unity3d.

Temat: Proste Animator, animacja postaci

Spis treści

Jeżeli nie używałeś do tej pory Unity3d:

#0 – Podstawy Podstaw

FPS Tutorial:

#1 – Tworzenie nowego projektu i narzędzie terenu

#2 – Sterowanie postacią

#3 – Życie, pancerz i wytrzymałość postaci

#4 – Regeneracja życia i energii. Efekty trafienia

#5 – Kamera z bronią i strzelanie

#6 – Przeładowanie i amunicja

#7 – Zbieranie przedmiotów

#8 – Druga broń

#9 – Rzut granatem i seria z karabinu

#10 – Przybliżenie i dziury po kulach

#11 – Proste AI przeciwnika

#12 – Animacja postaci przeciwnika, Animator

#13 – Menu główne gry. GUI

#14 – Ostatnie szlify i budujemy projekt

Teoria

Temat na który pewnie wiele osób czekało. Dzisiaj przyjrzymy się działaniu animatora i wprawimy w ruch nasze zombie z poprzedniego odcinka. Samo tworzenie animacji, jest procesem bardziej złożonym i pojedynczy wpis to zdecydowanie za mało, żeby ten proces omówić. Ponadto same animację oparte na szkieletach, najczęściej robione są w programie do grafiki 3D (np. Blender), a w Unity jedynie taką animacją sterujemy.

Oczywiście Unity, dysponuje opcją animowania różnych elementów. Przykładowo w części czwartej, robiliśmy prosty, zanikający efekt bycia trafionym, animując kanał alfa (przeźroczystość) tekstury. Jednak animowanie postaci jest nieco bardziej skomplikowane.

Ja grafikiem nie jestem, dlatego (przynajmniej na razie) nie pokażę wam procesu animowania od samego początku (czyli od wytworzenia modelu w Blenderze). Skupimy się na animatorze. Dlatego, ważne było pobranie dobrego modelu z AssetStore, który posiada własne animację. Jeżeli odcinek poprzedni, zrobiłeś dużo wcześniej, masz pewnie starszy model przeciwnika, który wstawiłem tam wcześniej – on nie dysponuje animacjami. Dzisiaj (13-02-2015) dokonałem aktualizacji i jest tam dostępny model, który w zestawie ma kilka podstawowych animacji. Dlatego przejdź na chwilę do odcinka 11 i podmień sobie model na aktualny. Stworzony skrypt będzie pasował w pełni, pamiętaj, żeby dodać dwa Collidery, odpowiednio je ustawić i aby przeciwnik posiadał tag „Enemy”.

Jeżeli masz odpowiedni model. Masz wszystko co nam się dziś przyda. Do pracy!

Przypisanie animacji – animator

Zaczynamy zabawę od dodania do animatora odpowiedniego kontrolera. Animator powinien być przypisany do postaci od razu. Jeżeli skorzystałeś z zasugerowanego przeze mnie modelu, poprawny model, który należy dodać do sceny znajduje się w folderze „Animations”. Poprawnie dodany model, jako komponent będzie posiadał Animator, z podanym Avatarem. Jedyne co musimy zrobić to uzupełnić pole Controller, przygotowanym kontrolerem, z folderu: „AnimatorController”. Powinno to wyglądać tak:

Dodanie kontrolera do animatora.
Dodanie kontrolera do animatora.

Teraz czas otworzyć kontroler. Wystarczy na niego kliknąć dwukrotnie, co powinno spowodować otwarcie takiego okna:

Kontroler animacji w Animatorze
Kontroler animacji w Animatorze

Na początku wygląda to dość strasznie. Jednak śpieszę z wyjaśnieniami. Każdy z prostokątów, symbolizuje konkretną animację. Atak, śmierć, otrzymanie obrażeń, bieg etc. Klikając na nie, możemy sprawdzić kilka parametrów. Nazwę animacji, tag, prędkość odtwarzania (1 to domyślny czas), konkretną animację jaka się uruchomi. Są to podstawowe, najważniejsze i wystarczające parametry. Możemy też obejrzeć listę przejść (Transitions). Czyli na jakie stany możemy przejść, z danego.

Obiekt stanu w Animatorze
Obiekt stanu w Animatorze

Możemy również wybrać samo przejście, czyli strzałkę wychodzącą z danego stanu do innego.

Ma

Opis przejścia w animatorze
Opis przejścia w animatorze

Mamy tutaj również opis przejścia, mamy wykres pokazujący jak animacje mają przechodzić jedna w drugą. Oraz warunki. Warunki mogą się opierać na parametrach, które były widoczne w lewym dolnym rogu na screenie animatora. Parametry są zwykłymi zmiennymi, typu int, bool, float lub Trigger. Możemy się do nich bezpośrednio odwołać w skrypcie i w ten sposób sterować animacją. Ostatnie okienko to podgląd animacji.

W samym Animatorze możemy jeszcze sterować warstwami, jednak dziś się tą kwestią nie będziemy zajmować.

Względem domyślnych ustawień, jedyne co ja zrobiłem, to usunąłem przejście z animacji śmierci (Death) do animacji nicnierobienia (idle0). Zapobiega to pewnemu problemowi, który sobie omówimy później. Przejście usuwamy wybierając je i naciskając delete.

Oczywiście, możemy sobie sami tworzyć nowe stany czy sekwencje. Aby dodać stan, wystarczy kliknąć prawym przyciskiem w wolnej przestrzeni i wybrać opcję Create State. Mamy tam trzy wybory. Pusta (Empty), z wybranego clipu (From selected Clip) oraz z drzewa mieszania (Blend Tree). Pierwsze dwa są dość oczywiste. Ostatni punkt, to sprytne narzędzie, które potrafi wymieszać ze sobą kilka animacji. W teorii powinno pozwolić animować otrzymanie obrażenia, bez przerywania animacji biegu. Jednak nie testowałem tego narzędzia dość dokładnie, więc na razie je pomijam.

Samą transakcję również możemy tworzyć. W tym celu klikamy prawym klawiszem myszy na danym stanie i wybieramy opcję Make Transition.

Ostatnią kwestią jest pomarańczowy prostokąt. Jest to domyślna animacja, czyli ta, która zostanie uruchomiona na początku. Można zmienić domyślny stan, klikając prawym klawiszem myszki na innym stanie i wybierając opcję „Set As Default”.

Zastanawiający może być jeszcze zielony (seledynowy?) prostokąt z hasłem „Any State”. Jak pewnie się domyślasz, aby móc przejść z jednego stanu, do innego, musi istnieć pomiędzy nimi ścieżka przejść. Jednak gdy mamy np. stan, do którego da się wejść bezpośrednio z każdego innego stanu, możemy go podpiąć do tego prostokąta. Należy też pamiętać, że każde przejście jest jednokierunkowe! Co oznacza, że jeśli chcesz stworzyć przejście w obie strony, trzeba przygotować dwa przejścia.

Gmerajmy w kodzie

Teorii tyle. Dość sporo, a to i tak minimalne podstawy, do tego twórca modelu odwalił za nas kawał roboty. Nam zostało rozpracowanie sterowania. W tym celu przechodzimy do naszego skryptu EnemyAI.cs

Do naszych zmiennych pomocniczych dodajemy kilka opcji:

Pierwszy to obiekt transform, z którego pobierzemy animator. String i AnimatorStateInfo posłużą do sterowania animacjami – żeby wiedzieć co włączyć musimy wiedzieć co obecnie trwa. Animator to komponent Animatora.

Należy oczywiście uzupełnić zmienną publiczną. Na chwilę wracamy do Unity. Zaznaczamy sobie obiekt ze skryptem. Przy zmiennej transforms, zmieniamy parametr size z 0 na 1 – chcemy tablicę z jednym elementem. Na nowo utworzoną pozycję dodajemy naszego zombie – czyli wystarczy go przeciągnąć z panelu Hierarchy lub Scene na wolne miejsce w skrypcie.

Po powstaniu obiektu, ustawiamy sobie wymagane zmienne.

Czyli pod obiekt animatora, wstawiamy już konkretny animator. A obecny stan obiektu ustawiamy na pusty.

Nasz kod, odpowiedzialny za „inteligencję” przeciwnika zmieniamy dosłownie w trzech miejscach:

Dodajemy w ifie warunek, że inteligencja działa tylko gdy przeciwnik ma więcej niż zero punktów życia. Gdy tego nie ma, dochodziło do sytuacji, gdzie zwłoki podążały za graczem i dalej go atakowały. AnimationSet to nasza mała funkcja (którą za chwilę napiszemy), odpowiedzialna za uruchomienie odpowiedniej animacji.

Jednak przed tym, dokładamy jeszcze jedną funkcję, a właściwie zdarzenie:

Funkcja OnTriggerExit, wykonuje się gdy jakiś obiekt opuści strefę wykrywania. Sprawdzamy czy to gracz, jeśli tak, ustawiamy animację na Idle. Przez to, gdy gracz ucieknie od zombie wystarczająco daleko, nie będzie on biegł w miejscu, a ponownie stanie.

Czas na funkcję animationSet:

Dokumentacja: Animator.GetCurrentAnimatorStateInfo ; AnimatorStateInfo.IsName ; Animator.SetBool

Wygląda bardzo groźnie, ale wiele tam nie ma. Najpierw pobieramy informacje na temat obecnego stanu animatora. Czyli dowiadujemy się, który stan obecnie jest animowany. Podajemy parametr 0, ponieważ funkcja jako parametr przyjmuje index warstwy. My mamy tylko jedną warstwę, dlatego 0.

animationReset, to również nasza funkcja, którą za chwilę podam i omówię. Ogólnie zeruje ona stany animatora, czyli tak jak by zatrzymuje wszystko.

Teraz mamy ifa, który sprawdza czy jakaś animacja obecnie jest wybrana, jeśli nie, wykonujemy kod. Jako currentState, zapisujemy podaną jako parametr animację (Np. Run), która ma być uruchomiona. Teraz, gdy kolejny kod będzie chciał wykonać animację, zobaczy że currentState jest różne od niczego i nie wejdzie nam w środek. Na samym końcu zerujemy tą zmienną, by pozwolić na dostęp kolejnej animacji.

Kolejny if sprawdza, czy obecnie trwająca animacja to bieg, oraz czy chcemy uruchomić animację inną niż bieg. Jeżeli tak, na true ustawia zmienną runToIdle0, czyli jeden z parametrów, które omawialiśmy wcześniej. W tym modelu, to te zmienne sterują tym, które przejście ma się wykonać.

Poruszać się możemy tylko zgodnie z przejściami. W takim układzie stanów i przejść jakie mamy w naszym modelu, Idle0 staje się przedpokojem, przez który musimy przejść zawsze gdy chcemy zmienić stan. Dlatego, gdy chcemy uruchomić atak, nie wystarczy ustawić zmiennej idle0ToAttack0 na true. Musimy najpierw opuścić pokój w którym byliśmy wcześniej, czyli np. stan biegu. Stąd ta funkcja.

Kolejny kod ustawia wejście już w odpowiedni stan. Przez to, że zmienne mają stałą budowę (stan1ToStan2), możemy sobie łatwo wygenerować string. Tym bardziej, że przez poprzedni kod, wiemy, że jesteśmy w stanie idle0. Funkcja Substring wycina odpowiedni fragment stringa. W pierwszym wypadku jest to od 0 znaku 1 znak, w drugim przypadku od znaku 1 do końca. Funkcja ToUpper zmienia literę na dużą. Parametry podajemy małą literą, a schemat parametrów wymaga dużej, stąd taka wariacja.

Funkcja animationReset:

Cudów tutaj nie ma. Ustawiamy praktycznie wszystkie parametry na false, jeśli obecnym stanem jest idle0, czyli gdy mamy nic nie robić. Jeżeli jesteśmy w dowolnym innym stanie, zerujemy tylko zmienną odpowiedzialną za powrót z biegu do idle0 – tym stanem może być bieg i nie chcemy od razu wracać, a biec dłuższy czas.

Została jeszcze modyfikacja funkcji otrzymania obrażeń:

Dokumentacja: Animator.CrossFade

Podmieniliśmy funkcję Destroy na animację śmierci. Dzięki temu, że usunęliśmy przejście powrotne, nie musimy się martwić, że postać mimo śmierci wykona inne animacje. Dobrym pomysłem, może być dodanie tutaj funkcji Destroy z opóźnieniem. Przy jednym przeciwniku nie ma to znaczenia. Jednak gdy przeciwników będzie spawnować w nieskończoność, a nie będziemy ich usuwać z gry po śmierci, w końcu zapchamy całkowicie pamięć komputera, a gra się zawiesi. Opóźnienie powinno być na tyle duże, żeby animacja się dokonała w pełni, a gracz miał czas np. odejść z obszaru śmieci przeciwnika – głupio wygląda jak wróg od tak znika na jego oczach bez powodu.

Druga funkcja jest ciekawsza. Używamy tam funkcji CrossFade. Ma ona dwa parametry. Pierwszy to stan animacji jaki chcemy uruchomić, drugi parametr to czas przejścia między animacjami. Funkcja CrossFade jest o tyle wygodna, że znajduje sobie ona sama drogę ze stanu w jakim się znajduje do wymuszanej funkcji. Przez to perfekcyjnie nadaje się jako funkcja do wymuszenia animacji trafienia przeciwnika.

Podsumowanie

Temat animowania jest bardzo złożony i trudny. Starałem się wyjaśnić wszystko w możliwe prosty sposób, ale wiem, że mimo to, wiele rzeczy może być niejasnych. Jeśli macie jakieś pytania, pytajcie w komentarzu, postaram się na nie odpowiedzieć, a do samej Animacji podejść w jakimś oddzielnym, temacie nastawionym tylko na animację.

Cały skrypt EnemyAI.cs

 

Poprzednia część <- #11 – Proste AI przeciwnika

Następna część -> #13 – Menu główne gry. GUI

  • O! Tego właśnie potrzebowałem. Dzięki bardzo.
    Wiesz może czy Unity pozwala na zabawę czasem? Na przykład przy włączeniu menu wszystko w grze dzieje się kilka(dziesiąt) razy wolniej?

  • Owszem. Jest gdzies quicktip o pauzowaniu, ktory opiera sie na zmniejszeniu uplywu czasu do zera. :)

  • Dasz zrobić poradnik o drzewku umiejętności albo że po np 10 sekundach celowania ono się wylancza a czas można zwiększyć w drzewku

    • Fajny pomysł. Będzie tutaj w sumie więcej zabawy z GUI niż samego skryptowania. Bo tak naprawdę sprowadza się to do zapamiętania wartości zmiennej i uzależnienia od niej umiejętności. Ale jak tylko skończę tutorial postaram się to ogarnąć. ;)

    • Dzięki przyda mi się to do mojej gry

  • Czy da się zmienić wartość w jednym skrypcie, który jest w innej scenie, za pomocą innego skryptu? Chodzi tu o to, żeby w scenie „menu”, w skrypcie była zmienna która patrzy czy gracz przeszedł daną misje, jeśli tak to wartość zmienia się na true :)

  • Fantyk PL

    Mam takie pytanie otóż zrobilem wszystko tak jak ty ale gdy włączam grę i goni mnie zombie i strzele w niego odpala mi sie animacja „wound” zabiera ma 10% hp a potem animacja „attack” i znów zabiera 10% hp z góry dzięki za pomoc ;)

    • Brakło w skrypcie jednej linijki. Gdy mamy:

      if (distance > attackDistance && !stateInfo.IsName („Base Layer.wound”)) {
      animationSet („run”);
      transform.Translate (Vector3.forward * walkSpeed * Time.deltaTime);
      } else {

      Zamiast else powinno być:
      } else if(distance <= attackDistance) {

      Bez tego, gdy przeciwnik nie biegł, to atakował, bez względu na dystans.
      Dzięki za uwagę. ;)

    • Fantyk PL

      Wielkie Dzięki :)

  • Fantyk PL

    Mogłbyś udostępnić mi cały skrypt EnemyAI na e-mail bo nadal mam dziwne błędy.
    emial: dkubiak132@interia.pl

    • Spodziewam się, że nie ostatni o to prosisz, dlatego na koniec wpisu wrzuciłem cały skrypt. ;)

  • misteroous

    Witam.

    IndexOutOfRangeException: Array index is out of range.
    EnemyAI.Start () (at Assets/Skrypty/EnemyAI.cs:19)

    Co może oznaczać ten błąd?
    Skopiowałem cały skrypt i wydaje mi się, że powinien działać.

    • Oznacza to, ze odwolujesz sie do elementu tablicy, ktory nie istnieje. Prawdopodonie nie ustawiles zmiennych publicznych z poziomu Unity.

    • Epos2110

      Witam.
      Szczerze mówiąc, nie wiem co należy uzupełnić w tych danych publicznych. Czy chodzi o te pola w Transforms? I jeśli tak, to czym je uzupełnić?
      Pozdrawiam!

    • Należy tam wstawić obiekt Zombie, który posiada animator. Czyli wystarczy jak przeciągniesz zombie z panelu Hierarchy na wolne miejsce w tabeli.

    • Epos2110

      Dzięki! ^^

  • Pingback: Unity3d FPS Tutorial #2 - Sterowanie postacią | mWin GameDev()

  • Pingback: Unity3d FPS Tutorial #4 - Regeneracja życia i energii. Efekty trafienia | mWin GameDev()

  • TK45

    Mam problem a mianowicie gdy odpala mi się jedna animacja to ona zatrzymuje wszystkie inne razem z trybem podglądu. Jak można to naprawić?

    • Wszystkie inne, czyli razem z innymi obiektami, czy wszystkie inne w obiekcie, w którym odbywa się animacja?

  • K11

    Mam pytanie jedno mianowicie jak przełaczac się miedzy postaciami.
    Tak aby jedna postać znikła a pojawiła się druga czyli np. z zająca zamienił się wilk :)

    • Możesz skorzystać z tej samej metody, którą wykorzystałem w tym tutorialu do zmiany broni. Tzn. obie postacie są childami jakiegoś nadrzędnego obiektu i sobie wędrują razem. Gdy gracz naciśnie odpowiedni przycisk, to chowasz postać A i wyświetlasz postać B.

  • radkowski

    Cześć,

    Mam pytanie, pobrałem z internetu animacje śmierci przeciwnika, jednak mam pewien problem. Mianowicie gdy przeciwnik już leży to jego rotacja względem wcześniejeszej „pozy” się nie zmieniła. Rodzi to problem gdyż kolider który ten przeciwnik posiada nie rotuję się i zostaję w pozycji pionowej, czy znasz może rozwiązanie tego problemu?

    • Technicznie to nie jest problem. Tzn. w momencie wykonania animacji, np. skoku na główkę czy położenia się, technicznie nie zmieniasz rotacji postaci. Tylko odgrywasz animację. Jeśli chcesz zmienić rotację postaci, to po za włączenie animacji, musisz tą rotację wykonać np. poleceniem Rotate.

      Jeśli dobrze domyślam się co chcesz zrobić, to mieć collider domyślny i potem taki „leżący”. Możesz w takim wypadku przeskalować collider po skończonej animacji.

  • Big Cyc

    Cześć mam takie pytanie. Zrobiłem gracza którym mogę się poruszać i wgl i wygląda jak człowiek.Wygląd zrobiłem w makehuman a animacje pobrałem z Raw Mocap Data. I nie wiem jak polonczyc animacjie biegu z biegiem i wgl:( Ktoś pomorze ?????

    • Najprościej będzie to zrobić Mecanimem. Niestety nie napisałem jeszcze tutoriala, który omawia to zagadnienie.

    • Big Cyc

      A czy byś mógł zrobić taki tut? Bardzo by mi się przydał a nigdzie nie mogę znaleść. Z góry dzięki:D

    • Zapewne zrobię, ale nie mam pojęcia kiedy. Próśb o różne tematy mam dziesiątki ;)

  • Nie wiem kiedy, ale się postaram ;)

  • Piotrow

    Witam, chciałbym zrobić obiekt, który skokowo zmienia swoją rotacje do ustalonej wcześniej wartości, więc nie interesuje mnie Rotate. Jak to zapisać, oraz jakie wartości można wstawiać w transform.rotation.

    • Jeśli chodzi o zmiany skokowe transform.Rotate będzie w sam raz. Jedynie wypadałoby to transform.Rotate wstawić sobie w pętle albo do coroutine i wykonywać co jakiś czas, dopóki nie osiągniesz zamierzonej rotacji.

      Do transform.rotation możesz wstawić Vector3, gdzie każdy z parametrów to float z przedziału: 0-360. :)

    • Piotrow

      przy użyciu rotate, końcowy wynik nie jest okrągły – przy wpisaniu transform.Rotate(0,0,180) wychodzi np. 180,009, dlaczego się tak dzieje, doda że obiekt nie ma żadnej fizyki, ani skryptu który mógłby to powodować

    • Wynika to ze specyfiki zmiennych typu float czy double – czyli zmiennoprzecinkowych. Żeby się tego pozbyć musiałbyś operować na intach, albo zwyczajnie zaokrąglać wynik.

  • Wiki Wisnowiecki

    a jak taką animację można zrobić ?

    • Wiki Wisnowiecki

      rozwiązałem problem sam a dla tych co też chcą mieć własnego gui skina to robisz public GUISkin np. guui . ostatnie dwie linijki gui usuwasz i zamiast tego piszesz GUI.skin = guui; i wchodzisz w kamere i przeciągasz własnego gui skina gotowe ;-)

      uuups miałem dwa poradniki na raz otwarte sorki a dla ciekawych drugi to był o pauzie.