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

Temat: Regeneracja życia i energii. Animacja trafienia.

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 broni 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

W poprzedniej części stworzyliśmy sobie paski życia, pancerza i energii. Życia ubywa po trafieniu, energii po biegu. Ale żadna z tych wartości się nie regeneruje. Również, w grach FPS, gdy zostaniemy trafieni, towarzyszy temu błyśnięcie ekranu na czerwono. Dodamy sobie teraz te efekty.

Regeneracja życia i energii.

Jak chcemy to zrobić? Wyjaśnię to na przykładzie paska zdrowia, a pasek staminy zostanie zrobiony analogicznie. Gdy zostaniemy trafieni, ustawiony zostanie licznik. Licznik zliczamy w dół, aż do zera. Gdy osiągnie tą wartość, znaczy, że możemy regenerować pasek. W grach, regeneracja życia, nie odbywa się natychmiast po trafieniu, a gdy chwilę przesiedzimy w bezpiecznym miejscu.  Taki licznik nam to zapewni. Następny krok to regeneracja paska, aż do maksymalnego poziomu życia. Tym razem, dzięki licznikowi, nie musimy dodatkowymi funkcjami przerywać regeneracji życia, gdybyśmy zostali trafieni, w czasie tej regeneracji.

Oczywiście, przyda nam się parę zmiennych pomocniczych:

private float canHeal = 0.0f;
private float canRegenerate = 0.0f;

Będą sprawdzały, czy nasza postać może się już leczyć lub regenerować wytrzymałość.

Teraz, czas przerobić trochę funkcję biegania i obrażeń, tak by nastawiać licznik. W funkcji takeHit dopisujemy jednego ifa:

void takeHit(float demage) 
{
	if(currentArmour > 0) {
		currentArmour = currentArmour - demage;
		if(currentArmour < 0) {
			currentHealth += currentArmour;
			currentArmour = 0;
		}
	} else {
		currentHealth -= demage;
	}

	if(currentHealth < maxHealth) {  // Tego ifa!
		canHeal = 5.0f;
	}

	currentArmour = Mathf.Clamp(currentArmour, 0, maxArmour);
	currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth);
}

Nie chcemy aktywować regeneracji, gdy stracimy pancerz. Jednak odebranie życia jest w dwóch miejscach. Dlatego, żeby nie dublować kodu, na koniec funkcji sprawdzamy, czy mamy mniej życia niż max, co oznacza, że powinniśmy się regenerować.

Czas poprawić bieganie:

void FixedUpdate () 
{		
	float speed = walkSpeed; // Ta linijka nie występuje dla Unity v.5.0
		
	if(chCont.isGrounded && Input.GetKey(KeyCode.LeftShift) && lastPosition != transform.position && currentStamina > 0) {
		lastPosition = transform.position;
		speed = runSpeed; // Ta linijka nie występuje dla Unity v.5.0
		currentStamina -= 1;
		currentStamina = Mathf.Clamp(currentStamina, 0, maxStamina);
		canRegenerate = 5.0f;
	}	
		
	chMotor.movement.maxForwardSpeed = speed; // Ta linijka nie występuje dla Unity v.5.0
}

Znów tylko ustawiamy licznik, gdy nasza postać biegnie. Niestety, ustawiamy to przy każdej klatce, ale będzie to bardziej optymalne, niż dodanie kolejnego ifa.

Teraz ustalmy zerowanie liczników. Zrobimy to, dodając do funkcji Update kilka linijek:

if(canHeal > 0.0f) {
	canHeal -= Time.deltaTime;
}
if(canRegenerate > 0.0f) {
	canRegenerate -= Time.deltaTime;
}

Jeżeli licznik został ustawiony, to co sekundę zmniejszamy go o 1. Czyli gdy w naszych przypadkach, ustaliliśmy wartość licznika na 5, oznacza to, że po 5 sekundach zacznie się regenerowanie.

Teraz zajmijmy się samym regenerowaniem, a załatwią to kolejne linijki w funkcji Update:

if(canHeal <= 0.0f && currentHealth < maxHealth) {
	regenerate(ref currentHealth, maxHealth);
}
if(canRegenerate <= 0.0f && currentStamina < maxStamina) {
	regenerate(ref currentStamina, maxStamina);
}

Gdy nasze liczniki są równe, bądź mniejsze od 0 (co kluczowe, bo przy zmniejszaniu ich, możemy uzyskać np. wartość -0.000000001. Wtedy samo przyrównanie do 0, nie uchwyciłoby takiego wyniku. Drugi warunek, to oczywiście sprawdzenie czy jest co regenerować.

Wewnątrz wywołujemy funkcję regenerate (napiszemy ją za chwilę), podając dwa parametry. Istotne tutaj jest słówko ref. Oznacza referencję.
[stextbox id=”info” defcaption=”true”]Referencja

Jest to zmienna, która zawiera informację, o położeniu innej zmiennej. Mówiąc obrazowo. Jeżeli podamy funkcji jako parametr zmienną “a”, o wartości 2, to wewnątrz funkcji mamy tylko wartość 2. Jednak, gdy podamy zmienną “a”, jako referencję, to wewnątrz funkcji, możemy zmodyfikować tą zmienną i będzie to widoczne na zewnątrz tej funkcji.[/stextbox]
Więc po co nam ta referencja? Chcemy naszą funkcję uczynić uniwersalną. Gdybyśmy podali jej tylko currentHealth, funkcja wiedziała by tylko, ile ona wynosi, ale nie wiedziałaby, na jakiej zmiennej ma operować, co uczyniłoby ją bezużyteczną.

Zobaczmy samą funkcję:

void regenerate(ref float currentStat, float maxStat)
{
	currentStat += maxStat * 0.005f;
	Mathf.Clamp(currentStat, 0, maxStat);
}

Magii tu nie ma. Dodajemy tylko pewien procent wartości maksymalnej do zmiennej currentStat, która jest parametrem – właśnie naszą referencją. Teraz jasno widać, po co była referencja. Gdybyśmy podawali tylko wartość, musielibyśmy mieć jeszcze 3 parametr, określający, którą statystykę mamy regenerować. Referencja, ściąga z nas ten obowiązek, a kod jest bardziej przejrzysty i czytelny. Funkcję Clamp już znamy.

Warto tutaj odnotować, że jeśli chcemy, żeby funkcja przyjmowała zmienną referencyjną, przy parametrze musi znaleźć się słówko ref.

W sumie tyle. Odpal swój program, pobiegaj i zobacz jak działa regeneracja. Jeśli pasek napełnia się za szybko lub za wolno, pobaw się procentem w funkcji regenerate.

Efekt obrażeń

UWAGA dla Unity3d v.5.0 i wyższych!

W Unity 5.0 poniższa metoda, może nie działać z powodu wprowadzonych w silniku zmian. W takim przypadku, warto zastosować metodę z mojego innego poradnika: Zmiana Opacity za pomocą kodu.

Najpierw musimy poczynić małe przygotowania. Potrzebujemy tekstury. U mnie jest prosty kwadrat 2x2px w kolorze czerwonym. Można to szybko zrobić nawet w paincie. Teraz tworzymy sobie nowy GUITexture [GameObject -> Create Other -> GUITexture]. Zaznaczamy go i dodajemy naszą teksturę w polu Texture.

Efekt obrażeń
Efekt obrażeń

Teraz, otwieramy sobie okno Animacji [Window -> Animation]. W nowym oknie, klikamy na Add Curve i zapisujemy sobie jakoś naszą animację. Np. HitAnimation. Teraz powinno pojawić się nam małe okienko. Rozwijamy GUITexture i klikamy w plus przy color.

Dodanie krzywej
Dodanie krzywej

Teraz klikamy w strzałkę przy nazwie, nowej pozycji, aby ją rozwinąć. Wybieramy z niej Color.a (czyli alpha, czyli przeźroczystość). Na samym dole wybieramy sobie tryb krzywych (Curves)

Edytujemy krzywą przeźroczystości
Edytujemy krzywą przeźroczystości

Teraz po prawej, powinieneś zobaczyć krzywą reprezentującą kanał alpha naszej tekstury. Kółkiem myszy możesz przybliżać lub oddalać ekran. Klikając w kółka możesz je przemieszczać. A gdy pojawi się kursor w kształcie dwóch strzałek skierowanych w przeciwne strony, możesz przesuwać cały wykres. Aby dodać klatkę kluczową, klikasz prawym i wybierasz Add Key.

Teraz należy ustawić krzywą tak by dała fajny efekt. Ja stworzyłem coś takiego:

Krzywa kanału alpha
Krzywa kanału alpha

Co nam daje taki wykres? Na początku animacji, tekstura jest mało widoczna, ale szybko skacze do wartości ok. 0.7, aby potem stopniowo znów zniknąć. Całość trwa 0.15 sekundy. Ustaw sobie krzywą jak chcesz, zapamiętać musisz tylko ile trwa.

Teraz wracamy na chwilę do MonoDevelop i dodajemy jedną zmienną:

public GUITexture hitTexture;

Gdy już to zrobiliśmy, przechodzimy do Unity i kończymy dzieło. Najpierw z naszej tekstury robimy prefabrykant. (Np. przeciągając z okna Chierarchy do okna Project). Teraz usuwamy GUITexture ze sceny (okno Chierarchy), a następnie utworzony prefabrykant, dodajemy do naszego skryptu, jako zmienna hitTexture:

Dodajemy teksturę
Dodajemy teksturę

Zostało już tylko wykonać animację w skrypcie. Wracamy do MonoDevelop i na samym początku funkcji takeHit() dodajemy:

Destroy(Instantiate(hitTexture).gameObject, 0.15f);

Kod jest bardzo prosty. Funkcja Destroy jest znana. Usuwa wskazany obiekt, po pewnym czasie, oczywiście czas jaki musisz podać, to czas trwania twojej animacji. A obiekt, jaki chcemy wtedy usunąć, to instancja naszej tekstury z animacją.

Zapisz skrypt, przejdź do Unity i przetestuj wszystko.

Małe porządki

Na tym etapie, potworzyła nam się masa nowych plików, dlatego polecam uporządkować foldery z assetami. Mogę polecić coś takiego:

  • Models – Na przyszłe modele 3D
  • Textures – Na textrury
    • GUI – Na tekstury do GUI
  • Scripts – Na skrypty
  • Animations – Na animacje
  • Prefabs – Na prefabrykanty
  • Materials – Na przyszłe materiały

Tereny i sceny trzymam w głównym folderze.

 Poprzednia część <- #3 – Życie, pancerz i wytrzymałość postaci

Następna część -> #5 – Kamera z bronią i strzelanie

 

Podoba Ci się? Udostępnij!