Unity3d QuickTip – czyli szybkie porady, rozwiązania częstych problemów i sztuczki w Unity3d!

Dzisiejszy odcinek: Jak zrobić cutScene? Part 2

Uwaga! Jest to poradnik typu QuickTip. Zatem skupia się on na osiągnięciu założonego celu. Zatem zakładamy że użytkownik zna na tyle program Unity3d, aby samodzielnie wykonać najprostsze czynności, jak np. dodanie modelu kostki do sceny czy dodanie modelowi jakiegoś komponentu. Jeżeli brakuje Ci tej podstawowej wiedzy, zapraszam do tutoriala:
Unity Tutorial – Podstawy

Teoria

Poprzednia część, gdzie przygotowujemy sobie scenę, dostępny jest pod magicznym linkiem. 

Przygotowujemy zmienne

W samym Unity wszystko sobie poukładaliśmy w poprzednim odcinku. Dlatego przechodzimy od razu do skryptu Cutscene.cs z poprzedniej części i deklarujemy zmienne:

public GameObject textObject;
public Transform runDestination;

private Text dialogue;
private CameraController camCon;
private float wordsPerSecond = 180 / 60;

Cóż tu mamy? Pierwsze dwie publiczne zmienne, to kolejno obiekt gdzie przechowujemy tekst, czyli u nas obiekt Text, będący childem obiektu Canvas. Druga zmienne publiczna, to pusty GameObject, do którego ma dobiec nasz czerwony NPC.

Zmienne prywatne. Pierwsza to obiekt typu Text. Tutaj jest pewna zawiłość. Mamy obiekt Canvas, ten wewnątrz ma obiekt Text, którego jednym z komponentów jest komponent Text, będący skryptem. Nim musimy operować, żeby zmienić dynamicznie wyświetlany tekst. W tej zmiennej będziemy sobie przechowywać do niego referencję. CameraController to skrypt, który napisaliśmy sobie wcześniej i powinien już być dodany. WordsPerSecond to prosta zmienna liczbowa, która oblicza ile słów czyta człowiek na sekundę – 180 wziąłem z jakiejś stronki, a dziele przez 60, bo parametr ze stronki podawał liczbę słów na minutę. Wykorzystujemy ją do tego, aby określić ile czasu ma się wyświetlać mówiony tekst.

W tym momencie, pewnie siedzisz i zastanawiasz się, czemu Text zaznaczony jest na czerwono. Wynika to z tego, że Unity nie wie o co nam chodzi, bo nie importujemy mu odpowiednich bibliotek. Dlatego na samej górze skryptu dopisujemy:

using UnityEngine.UI;

Teraz wszystko powinno się zgadzać.

Mamy nasze zmienne, czas sprawić, aby miały wartości.

void Start()
{
	dialogue = textObject.GetComponent<Text>();
	GameObject camConObj = GameObject.Find ("CameraController");
	camCon = camConObj.GetComponent<CameraController>();
}

Po pierwsze ustawiamy sobie pod naszą zmienną typu Text, komponent Text, czyli skrypt sterujący. – Brzmi to pokrętnie, ale mamy tutaj, aż trzy rzeczy nazwane tak samo i w sumie nie w naszej mocy to zmieniać. Kolejna linia to odnalezienie obiektu CameraController i pobranie z niego skryptu do zmiennej. Skrypt w raz z obiektem, utworzyliśmy w poprzedniej części.

Jak jesteśmy przy ustawianiu zmiennych, to skoczmy na chwilę do Unity i ustalmy nasze 2 zmienne publiczne.

Ustawiamy zmienne publiczne z poziomu Unity
Ustawiamy zmienne publiczne z poziomu Unity

Odpalamy scenkę

void OnTriggerEnter(Collider other) {
	if(other.tag.Equals("Player")) {
		StartCoroutine(startCutScene());
	}
}

Ten kawałek kodu mieliśmy w sumie już po pierwszej części z wyjątkiem kawałka StartCoroutine. Funkcja ta, uruchamia współprogram. Dzięki temu, możemy sobie korzystać z funkcji odliczającej czas, co nam się bardzo przyda.

Czas napisać samą funkcję startCutScene. Powinna ona istnieć, ale całkowicie zmienimy jej zawartość. Właściwie możesz sobie stworzyć swoją rozmowę. Podany kod jest tylko wzorcem.

IEnumerator startCutScene()
{
	greySpeaking ("Hello my friend!");
	yield return new WaitForSeconds((dialogue.text.Split().Length / wordsPerSecond) + 1);
	playerSpeaking("Oh my god! It's you!");
	yield return new WaitForSeconds((dialogue.text.Split().Length / wordsPerSecond) + 1);
	greySpeaking ("Yes! We meet again!");
	yield return new WaitForSeconds((dialogue.text.Split().Length / wordsPerSecond) + 1);
	epicRun();
	yield return new WaitForSeconds(3);
	redSpeaking ("Master, we must run!");
	yield return new WaitForSeconds((dialogue.text.Split().Length / wordsPerSecond) + 1);
	greySpeaking ("WTF?! OK!");
	yield return new WaitForSeconds((dialogue.text.Split().Length / wordsPerSecond) + 1);
	playerSpeaking ("What just happend?");
	yield return new WaitForSeconds((dialogue.text.Split().Length / wordsPerSecond) + 1);
	removeNPC();
	camCon.SelectCamera(0);
}

Pierwsze co się rzuca w oczy, to typ zwracany IEnumerator. Jest to typ konieczny, abyśmy mogli uruchomić funkcję za pomocą StartCoroutine i korzystać z odmierzania czasu.

Później mamy małą składankę funkcji.

  • greySpeaking, playerSpeaking, redSpeaking – Opisuje wszystkie 3, bo robią w sumie to samo. Czyli odpowiadają za to, która postać w danym momencie mówi i co mówi. Wywołanie funkcji, centruje kamerę na danej postaci, oraz wyświetla tekst jej wypowiedzi, podany jako parametr. Przedrostki sugerują o którą postać chodzi.
  • epicRun – funkcja, która sprawia, że czerwony NPC wbiega do pokoju.
  • removeNPC – usuwa NPCtów po rozmowie.
  • camCon.SelectCamera(0) – ustawia jako aktywną kamerę, kamerę zza pleców postaci, czyli umożliwiamy graczowi ponowne granie.

Na oddzielny akapit zasługują linijki:

yield return new WaitForSeconds((dialogue.text.Split().Length / wordsPerSecond) + 1);
yield return new WaitForSeconds(3);

Obie robią to samo. Odczekują pewien czas podany w sekundach. Druga linijka przez to staje się oczywista. Ale o co chodzi w pierwszej? Z naszego okienka tekstowego pobieramy tekst i dzielmy go wg. białych znaków (funkcja Split). Dostajemy przez to tablicę słów. Funkcja Lenght podaje nam liczbę elementów tablicy, czyli w efekcie liczbę słów. Dzielimy to przez nasz parametr, określający ile słów czyta człowiek w czasie sekundy, w efekcie otrzymując liczbę sekund jaka powinna każdemu wystarczyć na przeczytanie wyświetlanego tekstu. Na wszelki wypadek dodajemy jedną sekundę.

Funkcje

Czas opisać funkcję, które wykorzystaliśmy w kodzie:

void greySpeaking(string txt)
{
	speak (txt, "Joe", 2, Color.gray);
}

void playerSpeaking(string txt)
{
	speak (txt, "Player", 1, Color.blue);
}

void redSpeaking(string txt)
{
	speak (txt, "Pasent", 4, Color.red);
}

Tutaj mamy bardzo prosto, bo każda z funkcji wykonuje funkcję speak (napiszemy ją zaraz), z odpowiednimi parametrami. Parametry to po kolei: Tekst jaki ma się wyświetlić, imię osoby mówiącej, kamera tej osoby i kolor tekstu.

void speak(string txt, string person, int cam, Color col) 
{
	camCon.SelectCamera(cam);
	dialogue.text = person + ": " + txt;
	dialogue.color = col;
}

Funkcja odwala prawie całą robotę, a jest dość krótka. Po pierwsze ustawiamy kamerę na wybraną. – Jeżeli chodzi o indeksy jakie podałem w poprzednich funkcjach. Indeksy są przyznawane kolejno wraz z tworzeniem kamer. Trzeba sobie je określić metodą prób i błędów.

Dwie pozostałe linijki to ustawienie tekstu naszego komponentu tekstowego, oraz nadanie mu kolorów. Z racji, że dialogue odwołuje się do skryptu, który tym zarządza, można to zrobić w taki prosty sposób.

void epicRun()
{
	camCon.SelectCamera(3);
	GameObject redNPC = GameObject.Find("RedOne");
	UnityStandardAssets.Characters.ThirdPerson.AICharacterControl AIControl = redNPC.GetComponent<UnityStandardAssets.Characters.ThirdPerson.AICharacterControl>();
	AIControl.target = runDestination;
}

Funkcja z najdłuższą linijką ever! Pierwsza linia to znane nam ustawienie kamery. Potem znajdujemy sobie naszego czerwonego NPC. Ta długa linijka, jedyne co robi to pobranie komponentu (skryptu) AICharacterControl, który to umożliwia wykorzystanie NavMeshów do wysłania postaci w wybrane miejsce. NavMeshe przygotowaliśmy w poprzedniej części.

Dlatego jedyne co musimy w skrypcie zrobić, to podać mu miejsce docelowe, które wcześniej sobie podaliśmy jako zmienna publiczna. – W niektórych przypadkach, postać po dotarciu na miejsce może wariować (np. obracać się w miejscu). Rozwiązaniem może być usunięcie zmiennej target, gdy NPC dotrze na miejsce.

void removeNPC()
{
	Destroy(GameObject.Find("RedOne"));
	Destroy(GameObject.Find("GreyOne"));
}

Tutaj nie mogło być inaczej. Znajdujemy obiekty NPC i usuwamy.