Unity3d FPS Tutorial, czyli tworzymy własną grę FPS od podstaw z wykorzystaniem silnika Unity3d.
Temat: Zbieranie przedmiotów
Spis treści
Jeżeli nie używałeś do tej pory Unity3d:
FPS Tutorial:
#1 – Tworzenie nowego projektu i narzędzie terenu
#3 – Życie, pancerz i wytrzymałość postaci
#4 – Regeneracja życia i energii. Efekty trafienia
#5 – Kamera z bronią i strzelanie
#7 – Zbieranie przedmiotów
#9 – Rzut granatem i seria z karabinu
#10 – Celowanie i dziury po kulach
#11 – Przeciwnik z prostym AI, sztuczna inteligencja
#12 – Animacja postaci przeciwnika. Animator
#14 – Ostatnie szlify i budujemy projekt
Teoria
Ostatnim razem, zrobiliśmy sobie pełen magazynek. Teraz czas go napełnić, dlatego zrobimy sobie system zbierania amunicji, po wejściu na nią. Miał być też tu system zbierania nowej broni, ale doszedłem do wniosku, że ten etap, będzie wymagał trochę modyfikacji istniejącego kodu, dlatego zostawimy go sobie na oddzielny odcinek.
Przygotowanie
Potrzebny nam będzie jedynie obiekt, udający skrzynkę z bronią. Może to być zwykły cube, lub dowolny inny obiekt. Ja wykorzystałem zwykły cube z darmową teksturą.
Przygotowanie przedmiotu
Żeby całość sobie ułatwić, przygotujemy przedmiot jako prefabrykant.
[stextbox id=”info” defcaption=”true”]Prefabrykat
Jest to swojego rodzaju matryca obiektu. Przydatny przy dynamicznym tworzeniu obiektu. Jeżeli jakiś obiekt często występuje w grze, możemy uczynić z niego prefabrykant i właśnie prefaby wrzucić do gry. Dzięki temu, gdy coś zmienimy w prefabrykancie, wszystkie obiekty w grze, otrzymają tą zmianę.[/stextbox]
Aby z obiektu zrobić prefab, wystarczy przeciągnąć go z okienka Hierarchy do okienka Project. Będziemy potrzebować w nim dwie rzeczy. Po pierwsze collider. Wybierz w panelu Inspector: Component -> Physics -> Sphere Collider. Od razu zaznacz w nim opcję Is Trigger, oraz zwiększ nieco jego promień (Radius), tak by był większy od obiektu. Całość powinna wyglądać mniej więcej tak:
Jak mogłeś zauważyć na screenie, drugi krok to dodanie skryptu ammopack. Utwórz go teraz i przypisz do obiektu. Od razu możemy przejść w edycję skryptu ammopack.cs. Będziemy reagować tutaj na zdarzenia wejścia w trigger. Nasz plan działania jest prosty. Jeśli ktoś wejdzie w kolizję, sprawdzamy czy to gracz. Jeśli on, sprawdzamy, czy możemy zebrać amunicję. Jeśli tak, robimy to. Magiczny kod wygląda tak:
void OnTriggerEnter(Collider other) { if(other.tag.Equals("Player")) { Transform gun = other.transform.Find("Main Camera/Gun"); if(gun.GetComponent<Shooting>().canGetAmmo()) { gun.SendMessage("addAmmo", new Vector2(ammunition, gunType)); Destroy(gameObject); } } }
[stextbox id=”info” defcaption=”true”]OnTriggerEneter
Funkcja wykonuje się, gdy jakiś obiekt wejdzie w obszar collidera danego obiektu, który ma ustawione wyzwalanie (zaznaczona opcja IsTrigger. Jeżeli opcja nie jest zaznaczona, obiekt zareagowałby na zdarzenie OnCollisionEneter.
Wyróżniamy jeszcze dwa stany. OnTrigger – który wykonuje się w trakcie trwania kolizji z wyzwalaczem oraz OnTriggerExit – który wykona się gdy opuszczamy strefę kolizji.[/stextbox]
Najpierw sprawdzamy tag obiektu, z którym weszliśmy w kolizję (zaraz zajmiemy się ustawieniem tagu). Jeżeli nasz obiekt ma tag, Player, znaczy to że jest graczem. Warto zauważyć wykorzystanie funkcji Equals, którą poleca się przy wykonywaniu operacji porównani Stringów. Wykorzystując funkcję Find, znajdujemy obiekt Gun, który jest childem naszej kamery, dlatego zapis ze slashem. Kolejny krok to sprawdzenie, czy możemy zebrać amunicję. Służy do tego funkcja canGetAmmo w skrypcie Shooting (dopiszemy tą funkcję później). Aby się do niej dobrać, pobieramy komponent skryptu i wykonujemy funkcję. Funkcja zwraca bool (prawdę lub fałsz), dlatego nie musimy nic więcej dopisywać.
Jeśli wszystko się udało, wykonujemy funkcję SendMessage. Pozwala ona wywołać jakąś funkcję danego obiektu przez podanie jej nazwy. (Ten sposób nie jest w stanie odebrać wyniku działania funkcji, dlatego nie nadawał się do funkcji warunkowej if). Druga sprawa, to fakt, że funkcja SendMessage jako drugi parametr przyjmuje tylko jedną zmienną typu Object. Więc, aby przesłać dwie zmienne, musimy posłużyć się sztuczką i wysłać obiekt typu Vector2.
Na koniec niszczymy obiekt, tak by zniknął ze sceny gry – nie chcemy zbierać dwa razy tej samej amunicji.
W skrypcie przesyłamy dwie zmienne. Czas je dodać na początku skryptu:
public float ammunition = 25.0f; public float gunType = 1.0f;
Pierwsza zmienna to informacja, ile amunicji doda nam zebranie paczki. Dzięki ustawieniu jej na publiczną, możemy w scenie gry, modyfikować ten parametr dla każdej paczki oddzielnie. Druga zmienna to typ broni, dla której przeznaczona jest amunicja. Póki co go nie wykorzystamy, ale gdy dodamy kolejną broń, będzie z niego pożytek. Również publiczny, aby jeden skrypt, załatwiał nam zadanie dla każdego rodzaju paczki amunicji.
Teraz zostało lekkie poprawienie wyglądu naszych paczek. Zrobimy to tak:
void Update () { transform.Rotate(new Vector3(0, 1, 0)); }
Kod prosty jak koszulka dziecka. W każdej klatce, obracamy naszą paczkę w okół osi OY (która powinna być osią pionową). Dzięki temu, obiekty będą obracać się powoli wokół własnej osi.
Poprawa skryptu Shooting.cs
W naszym nowym skrypcie, używamy aż dwóch funkcji, które jeszcze nie istnieją. Czas je dodać. Przechodzimy więc do skryptu Shooting.cs męczonego w poprzednim odcinku.
Najpierw funkcja sprawdzająca, czy możemy zebrać amunicję:
public bool canGetAmmo() { if(currentAmmo == maxAmmo) { return false; } return true; }
Sprawa prosta. Jeśli mamy tyle amunicji, ile maksymalnie możemy mieć, to nie da się zebrać więcej (czemu graczowi ma zniknąć zapas amunicji, którego nie mógł zebrać, bo aktualnie ma maksymalną liczbę pocisków?). W przeciwnym wypadku pozwalamy mu na to.
Dodanie amunicji:
void addAmmo(Vector2 data) { int ammoToAdd = (int)data.x; if(maxAmmo - currentAmmo >= ammoToAdd) { currentAmmo += ammoToAdd; } else { currentAmmo = maxAmmo; } }
Parametrem funkcji musi być obiekt Vector2, bo tak sobie ustaliliśmy. Z racji, że w tym skrypcie operujemy intami, a Vector2 wymaga floatów, pierwszy krok to pobranie liczby amunicji jaką mamy dodać, z rzutowaniem jej na inta – int w nawiasie oznacza, że wymuszamy na obiekcie by stał się typu int, o ile to możliwe. Tzn. string “20” czy float 20.0f, zmienimy na int. Jednak String “20sda” wyrzuci błąd.
Sprawdzamy sobie, czy liczba kul jaką mamy dodać, jest mniejsza niż zapas jaki możemy zyskać. Jeśli tak, zwyczajnie dodajemy amunicję. Jeśli nie, nie pozwalamy graczowi wziąć bonusowego nadmiaru, ustalając po prostu stan amunicji na maksymalny dostępny.
Tagowanie
Nasz gracz, musi mieć tag Player, aby wszystko ładnie działało. Pierwszym krokiem, byłoby stworzenie tego tagu. Zrobilibyśmy to w panelu: [Edit -> Project Settings -> Tags and Layers]. Jednak nie musimy tego robić, ponieważ Tag Player jest tagiem domyślnym. Zostało ustawić graczowi ten tag, dlatego wybieramy obiekt First Person Controller i zmieniamy pole tag:
Podsumowanie
W tym odcinku, stworzyliśmy sobie system zbierania amunicji, który pozwala na szybkie dodawanie paczek. Zastosowaliśmy prefabrykant, dzięki czemu łatwiej będzie modyfikować całość. Dodatkowo, pojedyncze obiektu paczek amunicji pozwalają się łatwo modyfikować. Poznaliśmy dwie metody odwoływania się do funkcji innego obiektu, oraz poznaliśmy tagi i funkcję OnTriggerEnter.
Kolejny odcinek poświęcimy zbieraniu broni i zarządzaniu nią, czyli małym inwentarzem.