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

Temat: Menu główne gry. GUI

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

Nasz gra już nabrała kształtu. Mamy piękną wyspę, system strzelania, animowanego wroga, który “myśli”. Możemy zbierać przedmioty, mamy życie, pancerz, wytrzymałość. Finiszujemy! Dlatego dzisiaj zajmiemy się może mniej ciekawym, ale za to istotnym i często występującym aspektem gier. Jest nim oczywiście interfejs użytkownika, zwany z angielskiego GUI (Graphical User Interface).

Zrobić menu można na kilka sposobów. Albo wrzucając klikalne tekstury, albo operując na obiektach klasy GUI z Unity3d. Jak  można wnioskować po linkach, tematem już się zajmowałem, dlatego tutaj prawdopodobnie wyświetlimy tylko Logo gry z dodatkowym prostym, przyciskowym menu opartym na funkcjach GUI, dlatego, że niedawno to GUI było trochę modyfikowane przez zespół Unity.

Przy okazji poruszymy kwestię zmiany scen, oraz wyjścia z aplikacji.

Tworzymy nową scenę

Nową scenę możemy stworzyć wybierając w menu: [File -> New Scene]. Albo skorzystać ze skrótu klawiaturowego CTRL + N. Nową scenę od razu zapisujemy korzystając ze skrótu CTRL + S. Ja nową scenę nazwałem Menu.

Teraz kopiujemy sobie kilka elementów. Directional Light, Wodę i Teren. Zaznaczamy je w panelu Hierarchy na scenie gry, przechodzimy na scenę menu i wklejamy. (Nie tłumaczę jak to zrobić, bo robi się to tak samo jak w każdym systemie operacyjnym).

Teraz zostaje tylko dostosować kamerę. Dodajemy jej skybox [Component -> Rendering -> Skybox]. Wybieramy jeden z zaimportowanych do projektu Skyboxów, może być ten sam co do sceny gry, ale nie musi. Teraz ustawiamy kamerę główną, tak by by spoglądała z wysoka na wyspę. U mnie efekt końcowy wygląda mniej więcej tak:

Tutorial_13_01
Efekt ustawienia kamery w scenie Menu

 

Taka scena, będzie tłem dla naszego menu.

Dodajemy logo gry

Tworzymy sobie na początek skrypt, u mnie: Menu.cs. Dodajemy skrypt do kamery głównej i wchodzimy w edycję skryptu. Na start dodajemy sobie tylko jedną zmienną:

public Texture GameLogo;

Oczywiście, będzie to tekstura naszego logo. Jednak zanim ją dodamy, napiszemy skrypt, który ją wyświetli:

void OnGUI()
{
	GUI.DrawTexture (new Rect (0, 0, 800, 300), GameLogo);
}

Funkcja OnGUI odpowiada za wyświetlanie elementów interfejsu. Jest ona wywoływana przy każdym evencie (zdarzeniu) interfejsu, co sprawia, że może być wywołana nawet kilka razy w trakcie jednej klatki.

Sama funkcja DrawTexture, odpowiada za… narysowanie tekstur. Pierwszy parametr to obiekt typu Rect – czyli zwykły prostokąt, gdzie podajemy jego położenie w układzie X, Y. Oraz wymiary: Szerokość, wysokość. Drugi parametr to tekstura, którą mamy narysować.

Na wszelki wypadek podaje swoją teksturę:

Tekstura loga
Tekstura loga

Dodajemy menu

Na początek przydadzą się dwie zmienne:

public float buttonWidth = 300;
public float buttonHeight = 60;

private float buttonMargin = 20;

Tworzymy je publiczne, żeby wymiar przycisków, dało się w prosty sposób zmienić z poziomu Unity. Dodatkowy margin, to margines, który wykorzystamy do przesunięcia przycisków by nie wyświetlały się bezpośrednio na sobie.

Teraz sam kod, oczywiście wewnątrz funkcji OnGUI:

if(GUI.Button(new Rect (300, 300, buttonWidth, buttonHeight), "New Game")) {

}
if(GUI.Button(new Rect (300, 300 + buttonHeight + buttonMargin, buttonWidth, buttonHeight), "Options")) {
	
}
if(GUI.Button(new Rect (300, 300 + (buttonHeight + buttonMargin) * 2, buttonWidth, buttonHeight), "Exit")) {
	
}

Pierwsze co się rzuca w oczy to fakt, że przyciski wrzuciliśmy do ifów. Dzięki temu, wewnątrz ifa możemy określić co się ma wydarzyć, gdy przycisk zostanie naciśnięty. Sama budowa przycisku jest bardzo standardowa (jeśli chodzi o budowę elementów GUI). Czyli najpierw obiekt Rect ustalający pozycję, a potem treść przycisku. (Może być też obrazek, zamiast tekstu).

Druga być może nietypowa rzecz, do dodawanie dziwnych wartości do pozycji Y naszego przycisku. Gdybyśmy tego nie zrobili, przyciski pojawiły by się na sobie. A magiczne liczby to wysokość poprzedniego przycisku plus jakiś tam wymyślony przeze mnie odstęp, aby przyciski nie przylegały do siebie.

Już teraz, efekt powinien być następujący:

Menu po poprawkach
Menu po poprawkach

Teraz dodamy sobie kilka funkcji, tak aby przyciski nie były bezużyteczne:

if(GUI.Button(new Rect (300, 300, buttonWidth, buttonHeight), "New Game")) {
	Application.LoadLevel("Game");
}
if(GUI.Button(new Rect (300, 300 + buttonHeight + buttonMargin, buttonWidth, buttonHeight), "Options")) {
	
}
if(GUI.Button(new Rect (300, 300 + (buttonHeight + buttonMargin) * 2, buttonWidth, buttonHeight), "Exit")) {
	Application.Quit();
}

Pierwsza linia to funkcja LoadLeve, uruchamia ona podaną jako parametr scenę. Przyjmuje ona wartość tekstową (nazwę sceny), albo liczbową (indeks sceny). Drugi typ przejścia opiszemy sobie w kolejnej części, przy omawianiu builda. Jeśli jednak bardzo Cię to interesuje, możesz poczytać o tym dokładniej w innym poradniku.

Druga funkcja pozwala wyłączyć grę. Niestety nie zobaczymy jej działania, bo funkcja nie działa w edytorze Unity3d oraz w przeglądarce.

Poprawny wygląd

Menu już mamy. Nawet coś ono robi. Ale nie wygląda fajnie. Dlatego postaramy się je nieco podrasować. Wykorzystujemy w tym celu obiekty GUI Skin. Jego wykorzystanie jest banalne. Dodajemy sobie zmienną pomocniczą:

public GUISkin skin;

Od razu ją też wykorzystamy. Wystarczy w funkcji OnGUI dodać:

GUI.skin = skin;

Kod ten oznacza mniej więcej tyle, że cały układ GUI będzie korzystał z podanej w zmiennej skórki. Teraz czas ją dodać. Przechodzimy do Unity. Klikamy w oknie Project prawym klawiszem myszy na wolnym obszarze, a potem wybieramy Create -> GUI Skin (Przedostatnia pozycja). Skórkę nazywamy sobie jakoś i przypisujemy do skryptu. Skóra już teraz będzie działała, ale jej domyślne wartości pokrywają się z tym co mamy, więc nie zobaczymy efektów, jak sami czegoś nie zmienimy.

Ja u siebie usunąłem tło spod przycisków i ustawiłem czcionkę na Ariala rozmiar 50. Oraz ustawiłem inny kolor tekstu zależnie od stanu przycisku. Wyróżniamy główne 3 stany: Noraml – czyli stan spoczynkowy, domyślny. Hover – czyli gdy kursor znajduje się na przycisku. Active – czyli gdy przycisk jest aktywny, co dosłownie oznacza moment kliknięcia. Warto tutaj zauważyć, że jeśli przyciski nie posiadają tekstury tła, to zmiana koloru też nie nastąpi. Więc w przypadku gdy ja chciałem się całkowicie pozbyć tła spod przycisków, musiałem wstawić tam przeźroczystą teksturę. Opcję mojego GUI Skin wyglądają następująco:

Ustawienia GUI Skin
Ustawienia GUI Skin

Jak widać, skórka może określić masę innych elementów. Jednak opisanie każdego  z nich mija się z celem. Tutaj najlepszą nauką będzie pobawienie się tym komponentem w wolnej chwili. Już wiesz jak on działa, więc nie powinieneś mieć problemu z wykorzystaniem go do swoich potrzeb.

GUISkin vs GUIStyle

W Unity występują obie formy, ale właściwie czym się różnią? Otóż GUISkin jest zbiorem różnych GUIStyle. GUIStyle możemy przypisać bezpośrednio do konkretnego obiektu GUI. Np. przycisku. Wykorzystamy to, by wyszarzyć przycisk opcji, sugerując, że nie jest aktywny. W tym celu, dodajemy sobie zmienną pomocniczą:

public GUIStyle disabledButton;

Oraz delikatnie modyfikujemy kod przycisku:

GUI.Button(new Rect (300, 300 + buttonHeight + buttonMargin, buttonWidth, buttonHeight), "Options", disabledButton)

Dodaliśmy jedynie trzeci parametr. Teraz gdy wrócisz do Unity i zaznaczysz w panelu Hierarchy obiekt Main Camera w panelu Inspector powinno ukazać się coś takiego:

GUIStyle w panelu Inspector
GUIStyle w panelu Inspector

Jak widać, możesz sobie z panelu bezpośrednio zmodyfikować styl konkretnego przycisku.

Grupowanie

Istnieje jeszcze jedna przydatna rzecz, o której nie wspomniałem. Mianowicie grupowanie obiektów GUI. Najpierw to zrobimy, a potem wyjaśnię do czego to się może przydać. A kod modyfikujemy jedynie tak:

GUI.BeginGroup(new Rect (300, 300, buttonWidth, (buttonHeight + buttonMargin) * 3 ));

	if(GUI.Button(new Rect (0, 0, buttonWidth, buttonHeight), "New Game")) {
		Application.LoadLevel("Game");
	}
	if(GUI.Button(new Rect (0, buttonHeight + buttonMargin, buttonWidth, buttonHeight), "Options")) {
		
	}
	if(GUI.Button(new Rect (0, (buttonHeight + buttonMargin) * 2, buttonWidth, buttonHeight), "Exit")) {
		Application.Quit();
	}

GUI.EndGroup();

Dodaliśmy dwie istotne funkcję. Oczywiście BeginGroup oznacza otwarcie grupy, a EndGroup jej zakończenie. Zmieniliśmy też położenie w osi X i Y przycisków na 0, 0. Dlaczego? Ponieważ teraz położenie grupy, przejęło na siebie ustawienie całości, a przyciski ustawiamy względem początku grupy.

Brzmi zawile. Dlatego wyobraź sobie grupę jako pudełko. Przyciski ustawiamy w pudełku w jakimś tam układzie. I teraz bez względu na to, gdzie przeniesiemy pudełko, przyciski w nim będą zawsze ustawione tak samo.

Responsywność

Na koniec została jedna, często pomijana rzecz. Responsywność. Jest to nic innego jak dostosowanie rozmiaru GUI do rozdzielczości ekranu. Po co to? Gdy ustawimy szerokość przycisku na 400px, w rozdzielczości 1920×1080, będzie wyglądał fajnie. Ale gdy ktoś zagra w naszą grę przy rozdzielczości 800×600 przycisk zajmie połowę szerokości ekranu!

Robimy to wykorzystując prostą proporcję. Najpierw musimy założyć sobie nasza natywną rozdzielczość, czyli tą, pod którą dedykujemy grę. Ja u siebie zakładam fullHD, czyli 1920×1080. Dlatego dokładam taki kod:

void Start()
{
	buttonWidth = (buttonWidth * Screen.width) / 1920;
	buttonHeight = (buttonHeight * Screen.height) / 1080;
	buttonMargin = (buttonMargin * Screen.height) / 1080;
}

Jak wspominałem, bardzo prosta proporcja, gdzie szerokość/wysokość przycisku, mnożymy przez aktualną wysokość/szerokość ekranu i dzielimy przez natywną. Tym sposobem otrzymujemy rozmiar przycisku dla odpowiedniej rozdzielczości.

Oczywiście to samo wypada zrobić z położeniami na osi X i Y różnych obiektów. Jednak to, zostawiam jako Twoje zadanie domowe, aby nie rozciągać tej i tak już długiej części.

Podsumowanie

Dzisiaj poznaliśmy podstawowe zagadnienia związane z obsługą GUI. Poznaliśmy opcję grupowania, obsługę przycisków, czy rysowanie tekstur. Oczywiście Unity oferuje znacznie więcej kontrolek i opcji, jednak do stworzenia typowego menu gry, nie potrzebujemy nic więcej.

 

Poprzednia część <- #12 – Animacja postaci przeciwnika, Animator

Następna część -> #14 – Ostatnie szlify i budujemy projekt

Podoba Ci się? Udostępnij!