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

Temat: Rzut granatem i seria z karabinu

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 – Celowanie i dziury po kulach

#11 – Przeciwnik z prostym AI, sztuczna inteligencja

#12 – Animacja postaci przeciwnika. Animator

#13 – Menu główne gry. GUI

#14 – Ostatnie szlify i budujemy projekt

Teoria

Ostatnio dodaliśmy sobie drugą broń, jaką był karabin. Teraz zmienimy go w prawdziwy karabin, dodając opcję strzelania serią. Dodatkowo wzbogacimy grę o rzut granatem!

Co musisz sobie przygotować? Model granatu oraz dźwięk wybuchu: FPSTutorial#9 – Assets

Strzelanie serią

Jednak zaczniemy od poprawy systemu strzelania. Wystarczy kilka drobnych zmian w kodzie skryptu Shooting.cs. Najpierw dodajemy sobie kilka zmiennych pomocniczych:

public bool automatic = false;
public float shotDelay = 0.5f;
private float shotDelayCounter = 0.0f;

Zmienna boolowska określa, czy dana broń może strzelać serią. Ustawiona na publiczną, żeby każdej broni, móc dobrać ten parametr niezależnie. Pozostałe dwie to liczniki odpowiadające za opóźnienie. Ich zastosowanie wyjaśnię, gdy pojawią się w kodzie.

Jedyne co zmienimy w skrypcie to funkcja Update, gdzie znajdował się kod odpowiedzialny za wystrzał. Wygląda on teraz tak:

if(currentClip > 0 && !isReloading) {
	if((Input.GetButtonDown("Fire1") || (Input.GetButton("Fire1") && automatic)) && shotDelayCounter <= 0) {
		shotDelayCounter = shotDelay;
		currentClip--;
		pistolSparks.particleEmitter.Emit();
		audio.Play();
		
		if (Physics.Raycast(transform.position, fwd, out hit)) {
			if(hit.transform.tag == "Enemy" && hit.distance < range) {
				Debug.Log ("Trafiony przeciwnik");
				
			} else if(hit.distance < range) {
				Debug.Log ("Trafiona Sciana");
			}
		}
	}
}

Zmieniły się w sumie tylko dwie pierwsze linię. Rozbiłem naszego ifa, na dwa, tylko po to, żeby kompletny if nie miał 120 znaków długości. Posiadanie conajmniej jednej kuli i brak przeładowywania jest niezależnym od trybu strzelania warunkiem, więc wyciągnąłem go na zewnątrz.

Warunek z kolejnego ifa, omówimy częściami. Może nie widać tego od razu, ale if składa się z jednego złożonego warunku && prosty warunek. Warunek prosty to:

shotDelayCounter <= 0

Czyli licznik opóźnienia musi wynosić 0. Złożony warunek natomiast:

(Input.GetButtonDown("Fire1") || (Input.GetButton("Fire1") && automatic))

Posiada dwa warunki oddzielone znakiem OR (a angielskiego: lub), który reprezentują dwie pionowe kreski (||). Aby taki warunek został spełniony, co najmniej jedna ze stron musi być prawdą. Po lewej został nasz stary kod, mówiący o pojedynczym naciśnięciu przycisku. Drugi warunek znów jest złożony. Jest realizowany gdy przycisk strzału jest wciśnięty, oraz gdy broń zezwala na strzał serią.

Ostatni zmieniony kod, to dodanie do licznika opóźnienia, czasu opóźnienia dla danej broni. Musimy dodać opóźnienie z jednego powodu. Gdy tego nie zrobimy, funkcja GetButton, wykona się praktycznie w każdej klatce gdy mamy wciśnięty klawisz myszy. Oznacza to tyle, że amunicja z karabinu skończy się w kilka sekund. Dodając opóźnienie, zwiększamy realizm działania broni.

Na koniec zostało minimalizować ten licznik. Gdzieś w funkcji Update należy dopisać:

if(shotDelayCounter > 0) {
	shotDelayCounter -= Time.deltaTime;
}

Jeżeli licznik jest większy od zera, to go zmniejszamy. Nic trudnego. Teraz wystarczy wrócić do Unity i ustawić pistoletowi i karabinowi odpowiednie wartości nowych zmiennych. U mnie sprawdziło się: Opóźnienie dla pistoletu: 0.1 i opóźnienie dla karabinu 0.5 oraz oczywiście opcja strzelania serią.

Przygotowanie granatu

Na samym początku musimy sobie dobrze przygotować prefabrykant granatu. Dodajemy obiekt do sceny, następnie dodajemy mu dwa komponenty: Rigidbody [Component -> Physics -> Rigidbody] oraz collider [Component -> Physics -> Sphere Collider]. Dzięki temu, granat będzie pod wpływem fizyki, oraz nie będzie przenikał przez ściany.

Mając taki granat, przeciągamy go do pola Project, tworząc Prefabrykant. Można teraz usunąć granat ze sceny. Operować będziemy już tylko na prefabrykancie. Teraz czas stworzyć skrypt, który obsłuży granat. Tworzymy skrypt: Grenade.cs i przypisujemy go do prefabrykantu (wystarczy przeciągnąć).

Kod ma tylko 4 pomocnicze zmienne:

public float toExplode = 4.0f;
public GameObject explosion;
public AudioClip explosionSound;
public AudioSource explosionHolder;

Pierwsza to licznik, odliczający po jakim czasie od rzutu granat wybuchnie. Druga, to emiter cząsteczek, który zagwarantuje efektowny wybuch. Trzecia to dźwięk wybuchu, a ostatnia to emiter dźwięku (zaraz do tego dojdziemy). Skrypt nie obsługuje póki co obrażeń wroga. Zajmiemy się tym później. Teraz czas na kod w funkcji Update, czyli faktyczne działanie granatu:

void Update () {
	toExplode -= Time.deltaTime;

	if(toExplode <= 0) {
		AudioSource explosionS = Instantiate(explosionHolder, transform.position, transform.rotation) as AudioSource;
		explosionS.clip = explosionSound;
		explosionS.Play();
		Instantiate(explosion, transform.position, transform.rotation);
		Destroy(gameObject);
	}
}

Po utworzeniu, timer zaczyna odliczać w dół. Jeśli licznik zejdzie do zera, oznacza to, że granat ma wybuchnąć. Korzystamy tutaj, aż dwukrotnie z funkcji Instantinate, która pozwala utworzyć dynamicznie dowolny obiekt. Pierwsza zmienna to obiekt, który tworzymy, druga to pozycja w jakiej ma się pojawić, a trzecia to rotacja.

Na samym początku tworzymy sobie instancję obiektu AudioSource – teraz wyjaśnia się explosionHolder – ustawiamy go w miejscu gdzie znajduje się granat. Jako clip ustawiamy mu dźwięk wybuchu i uruchamiamy ten dźwięk. Później również tworzymy instancję emitera cząsteczek wybuchu. Na koniec niszczymy obiekt.

Nasuwa się pytanie: “Po co tyle zachodu?”. Musimy stworzyć emiter dźwięku i cząstek w ten sposób, ponieważ gdyby były zależne od samego obiektu granatu, to w momencie wywołania funkcji Destroy, emiter i dźwięk urwałyby się nagle. Taki ruch sprawia, że dźwięk odegra do końca, tak jak emisja cząsteczek.

Teraz czas uzupełnić dane skryptu z poziomu Unity: GrenadeSound to zwykły dźwięk wybuchu (dostępny w paczce dostarczonej przeze mnie na początku wpisu), explosion, znajdziesz w paczce standardowych assetów: Standard Assets -> Particles -> Legacy Partilces -> Explosion. Explosion Holder, musisz sobie utworzyć. Będzie to o tyle proste, że jest to zwykły pusty gameObject (Ctrl + Shift + N), który ma dodany komponent AudioSource [Component -> Audio -> AudioSource].

Granat powinien pięknie działać. Czas nim rzucić.

Rzut granatem

Aby wykonać rzut stworzymy prosty skrypt (GrenadeThrow.cs) przypisany do obiektu MainCamera. Jeżeli chodzi o zmienne, znów jest prosto:

public Rigidbody grenade;

private int currentGrenades = 2;

Pierwsza zmienna jest typu Rigidbody, bo oczekujemy takiego komponentu w prefabie, który chcemy rzucić jako granat. Druga, to zwykły int trzymający maksymalną liczbę granatów.

void Update () {
	if(Input.GetKeyDown(KeyCode.F) && currentGrenades > 0) {
		Rigidbody clone = Instantiate(grenade, transform.position, transform.rotation) as Rigidbody;
		clone.AddForce(transform.TransformDirection(Vector3.forward * 1000));
		currentGrenades--;
	}
}

Sam kod do rzutu jest banalnie prosty. Gdy gracz naciśnie F i ma jakieś granaty, tworzymy sobie dobrze znaną nam funkcją Instantinate granat (Dzięki przypisaniu skryptu do kamery, możemy skorzystać z rotacji i pozycji obiektu, do którego jest przypisany skrypt, co w efekcie da rzut granatu w dobrym kierunku). Drugi krok to rzucenie granatu. Dzięki temu, że granat ma Rigidbody, możemy użyć funkcji AddForce, która go “pchnie” w odpowiednim kierunku. AddForce, można sobie wyobrazić jak kopnięcie piłki. Jednorazowo nadaje kierunek i moc poruszania obiektu.

Przez to, że mamy dobrze ustawiony skrypt, możemy użyć Vector3.forward, który wskazuje przód, bez zastanawiania się, w którą stronę mamy pchnąć obiekt. Na koniec zmniejszamy zapas granatów.

Teraz wystarczy w Unity, jako zmienną Greanade, przypisać wcześniej przygotowany prefab.

Podsumowanie

Dzisiaj zmieniliśmy nasz jednostrzałowy karabin, w strzelającą seriami maszynę do zabijania. Dodatkowo stworzyliśmy opcję rzutu granatem. Granat póki co nie zadaje obrażeń, a gracz posiada tylko 2 sztuki.

Jako zadanie domowe, możesz teraz dorobić skrypt zbierania dodatkowych granatów, na podstawie tutoriala, gdzie zbieraliśmy amunicję lub broń. Powodzenia!

Poprzednia część <- #8 – Druga broń

Kolejna część -> #10 – Celowanie i dziury po kulach

Podoba Ci się? Udostępnij!