Ten wpis będzie poświęcony tworzeniu
planszy do gry i proces ten będę starał się opisać od podstaw w
taki sposób, aby był zrozumiały także dla osób, które
dotychczas nie miały do czynienia z programowaniem. Na dole tego
wpisu zamieszczam link do omawianego tutaj kodu.
Przygotowanie sceny
Na początku uruchamiamy Unity i
tworzymy nowy projekt.
Program oferuje nam wybór pomiędzy
wersją 3D i 2D, ale te opcje różnią się tylko opcjami kamery,
które można przestawić nawet w trakcie tworzenia gry, co sprawia
że wybór ten jest odwracalny. Ja w swoim obiekcie będę korzystał
z wersji 3D, która doda do gry troszkę głębi. Kolejną opcją
która rzuca się w oczy jest możliwość dodania do gry gotowych
paczek assetów, ale takie działanie także możemy wykonać w
trakcie pracy nad projektem. Ja dotąd nie korzystałem z takich
materiałów, uważam że warto jest nauczyć się samemu
przygotowywać materiały do gry.
Po utworzeniu projektu ukaże się nam
scena, która powinna zawierać kamerę i źródło światła.
Obiekty te powinny być wyświetlane w oknie hierarchii:
Od razu dodam, że takie okna można
dowolnie przestawiać, dzięki czemu można dostosować program do
siebie. Scenę polecam od razu zapisać, oraz zapisywać ją za
każdym razem gdy skończymy modyfikować znajdujące się na scenie
obiekty, dzięki czemu nie utracimy naszego czasu w razie problemów
technicznych.
Gdy przygotowaliśmy scenę do pracy,
nareszcie możemy przejść do podstawowych rzeczy, a dokładniej
przygotowaniem samej planszy do gry. Można to wykonać na dwa
sposoby:
a) Ustawiając i modyfikując obiekty
za pomocą edytora Unity.
b) Ustawiając i modyfikując obiekty
za pomocą skryptów.
Zaletami pierwszej opcji jest to, że
na bieżąco widzimy efekty swojej pracy, oraz nie potrzebujemy
żadnych umiejętności związanych z programowaniem. Druga opcja ma
nieco więcej zalet: pozwala generować obiekty za pomocą własnych
schematów (co mocno ułatwia tworzenie obiektów o regularnych
odstępach), pozwala na szybkie wprowadzanie zmian, oraz od razu
wrzuca informacje o obiektach do naszego skryptu. Wykonamy więc to
drugą metodą.
Najpierw musimy utworzyć skrypt. Aby
tego dokonać musimy wybrać w menu Assets → Create → C# Script.
Nowy skrypt pojawi się w oknie projektu. Nadajemy jemu nazwę i
otwieramy go. Ukaże się nam podstawowy szablon tego kodu:
O czym trzeba pamiętać: nazwa klasy
musi być taka sama, jak nazwa skryptu, jeśli więc zmienimy nazwę
skryptu, musimy zmienić też nazwę klasy.
Elementem na który zwrócimy tutaj
uwagę jest funkcja „void Start ()”. Jest to funkcja która jest
automatycznie uruchamiana w trakcie pierwszego uruchomienia się skryptu,
czyli jeśli przypiszemy ten skrypt do obiektu znajdującego się w
edytorze, to ta funkcja uruchomi się przy starcie gry (ponieważ
obiekt pojawia się na starcie gry i jest wtedy do niego przypisywany
skrypt). Jest to więc funkcja która nada się do generowania
planszy. Inną funkcją która może spełniać taką rolę jest
funkcja „void Awake ()”, która uruchamia się gdy skrypt zostanie przypisany do obiektu, czyli nawet przed funkcją Start. W małych
projektach wybór między tymi dwoma funkcjami nie ma dużego
znaczenia, ale w przypadku generowania planszy funkcja Awake wydaje
się być lepszym pomysłem.
Treść funkcji znajduje się między
jej klamrami i tylko ten fragment skryptu będzie wykonany przez
funkcję. Jeśli postawimy gdzieś dwa ukośniki „//”, to
fragment kodu po ich prawej stronie będzie ignorowany przez program.
Jest to tzw. komentarz, który służy głównie do zapisywania
notatek w kodzie.
Tworzenie obiektu
Najpierw zajmiemy się stworzeniem
planszy pełniącą rolę tła. Obiekty można tworzyć za pomocą
tzw. prefabów (które są obiektami zapisanymi w materiałach gry),
albo za pomocą specjalnych funkcji. Prefaby są znacznie lepszym
wyborem w przypadku tworzenia złożonych obiektów (jak np. postacie
z gotowymi modelami, teksturami i skryptami), ale w przypadku
prostych obiektów równie dobrze można posłużyć się samym
kodem. Przykładowo aby stworzyć kwadrat wystarczy jedna prosta
linijka kodu:
GameObject.CreatePrimitive
(PrimitiveType.Quad);
W kodzie wszystko jest zapisywane od
ogółu, do szczegółu, co wyjaśnię na podstawie tej linijki kodu.
Pierwsze do czego musimy się odwołać, to klasy „GameObject”,
która zawiera w sobie różne funkcje związane z obiektami w grze.
Następnie chcemy się odwołać do konkretnej funkcji, czyli do
funkcji „CreatePrimitive”, służącej do tworzenia prostych
obiektów, a w nawiasie wpisujemy parametr, czyli w tym przypadku typ
prostego obiektu, który chcemy stworzyć. Całe polecenie kończymy
średnikiem. My jednak chcemy nie tylko stworzyć obiekt, lecz także
go modyfikować, a to staje się łatwiejsze, jeśli przypiszemy ten
obiekt do zmiennej.
GameObject
Clone = GameObject.CreatePrimitive
(PrimitiveType.Quad);
Tworzymy więc zmienną typu
„GameObject”, a następnie nadajemy jej jakąś prostą nazwę,
jak np. „Clone”. Od teraz za każdym razem gdy w tej funkcji
użyjemy nazwę naszego obiektu, to program będzie ją traktował
jako odwołanie do obiektu. Znak równości w tej linijce kodu
oznacza, że to co jest po lewej staje się tym, co jest po prawej.
Modyfikacja obiektu
Skoro stworzyliśmy obiekt, możemy go
modyfikować. Pierwszą rzeczą którą chcemy zmodyfikować jest
„ojciec” obiektu:
Clone.transform.parent
= transform;
„Transform” jest tutaj komponentem
zawierającym podstawowe informacje dotyczące obiektu. Jeśli przed
wyrażeniem „transform” nie znajduje się żaden obiekt (co ma
miejsce po prawej stronie znaku równości), to program odwołuje się
do obiektu posiadającego ten skrypt. Jak w prosty sposób zrozumieć
czym w kontekście programowania jest „ojciec”? Ojciec w Unity
jest jak np. autobus w którym zdarza nam się jeździć w życiu
codziennym. Jeśli autobus zacznie się przemieszczać, my
przemieszczamy się razem z nim, a jeśli zaczniemy chodzić po
autobusie, to przemieszczamy się relatywnie względem niego, a nie
względem świata. Gdy wyjdziemy z autobusu, przestaje on być naszym
ojcem i zaczyna nim być ziemia. Warto pamiętać, że jeśli w Unity
jakiś obiekt nie posiada ojca, to wtedy scena jest traktowana jako
jego ojciec.
Kolejną rzeczą jaką chcemy wykonać
jest ustalenie jakiejś konkretnej pozycji obiektu. Tak wyglądałoby
ustawienie naszego obiektu w punkcie (0, 0, 0) względem naszego
ojca:
Clone.transform.localPosition
= new
Vector3
(0, 0, 0);
Pozycję obiektu można przedstawiać
za pomocą zmiennej „position” lub „localPosition”. Pierwsza
zmienna odpowiada za pozycję globalną (względem środka sceny),
zaś druga pozycja odpowiada za pozycję lokalną (względem tzw.
„ojca”). Pozycje obiektów są zapisywane za pomocą wektorów o
3 współrzędnych (x, y, z) i dlatego utworzyliśmy nowy „Vector3”.
Standardowo „x” oznacza szerokość, „y” oznacza wysokość,
a „z” oznacza głębokość.
Ostatnią rzeczą jaką chcemy
zmodyfikować jest skala obiektu:
Clone.transform.localScale
= new
Vector3
(20, 10, 1);
Wszystko przebiega tak samo jak w
przypadku modyfikacji pozycji obiektu. Każda z współrzędnych
powinna być większa od zera, jeśli chcemy, aby nasz obiekt był
widoczny. Co warto wiedzieć: jeśli skala ojca obiektu zostanie
zwiększona, to automatycznie zostanie zwiększona skala obiektu.
Cztery linijki kodu i mamy utworzony
prostokąt o pożądanych przez nas właściwościach.
Tworzenie i modyfikacja serii obiektów
Tym razem zajmiemy się tworzeniem
planszy o wymiarach 6x6 składającej się z kwadratowych pól. Aby
tego dokonać zastosujemy pętlę "for":
for
(int
x = 0; x < 6; x++) {
}
Przed pierwszym średnikiem następuje
deklaracja zmiennej całkowitej (int) o nazwie „x”, której
przypisujemy wartość zero. Dzieje się to przy starcie pętli.
Następny fragment nawiasu mówi nam, że pętla będzie się
wykonywała tak długo, jak długo x będzie mniejszy od 6, czyli
jest to instrukcja warunkowa. Ostatni fragment nawiasu mówi nam, że
x zostaje zwiększone o 1 za każdym razem, gdy wykonane zostanie
jedno przejście pętli. „x++” może też być zapisane jako „x
= x + 1” (co oznacza, że x staje się równe wartości „x + 1”),
albo „x += 1” (co oznacza, że x zostaje zwiększone o 1). Każdy
zapis oznacza właściwie to samo, ale każdy woli to zapisywać na
swój sposób.
Dzięki pętli jesteśmy w stanie
wykonać tą samą instrukcję kilka razy. Aby ułatwić nam
zrobienie planszy o wymiarach 6x6 warto jest umieścić w pętli
kolejną pętlę:
for
(int
x = 0; x < 6; x++) {
for
(int
y = 0; y < 6; y++) {
}
}
A następnie możemy zamieścić
wewnątrz fragment kodu odpowiedzialny za tworzenie pól:
for
(int
x = 0; x < 6; x++) {
for
(int
y = 0; y < 6; y++) {
//
Tworzenie kwadratu i przypisanie go do zmiennej
Clone
= GameObject.CreatePrimitive
(PrimitiveType.Quad);
//
Ustalenie ojca
Clone.transform.parent
= transform;
//
Ustalenie lokalnej pozycji
Clone.transform.localPosition
= new
Vector3
(-2.5f + x, -1.5f + y, -0.01f);
//
Ustalenie skali
Clone.transform.localScale
= new
Vector3
(0.9f, 0.9f, 1);
// Ustalenie koloru
Clone.GetComponent<Renderer>
().material.color = new
Color
(0.3f, 0.3f, 0.3f);
}
}
Łatwo można zauważyć, że ten kod
różni się nieco od poprzedniego. Pierwszą różnicą jest dodanie
przeze mnie komentarzy – nie mają one wpływu na działanie
programu, ale mogą one pomóc niektórym łatwiej orientować się w
kodzie. Drugą różnicą jest korzystanie z liczb
zmiennoprzecinkowych typu float, które zapisuje się korzystając z
kropki oraz dodając na końcu literę „f” (np. -2.5f). Kolejną
różnicą jest ustalenie zależności pomiędzy pozycją obiektów,
a zmienną odpowiadającą za sterowanie pętlą – dzięki czemu
pętle pozwalają nam na tworzenie pól rzędami i kolumnami.
Dodatkowo zmieniłem też pozycję względem współrzędnej „z”,
aby pola znajdowały się bliżej kamery niż tło, dzięki czemu
będziemy mieć pewność, że będą widoczne. Ostatnią różnicą
jest zmodyfikowanie koloru obiektu, aby był innego koloru niż tło
i był dzięki temu widoczny. Dokonałem tego odwołując się do
komponentu „Renderer”, by następnie odwołać się do materiału
i jego koloru. Kolor można wyrażać za pomocą RGB (bez
przeźroczystości), lub RGBA (z przeźroczystością), sam typ
zapisu koloru jest ustalany na podstawie liczby argumentów podanych
w nawiasie. Wartości kolorów są standardowo wyrażane od 0 do 1,
gdzie 0 jest brakiem określonego kolor, a 1 wynosi 100% tego koloru.
Warto jeszcze pamiętać, że nie każdy materiał obsługuje kanał
alfa (przeźroczystość), więc jeśli chcemy utworzyć częściowo
przeźroczysty obiekt, musimy mu najpierw ustalić odpowiedni
materiał.
Przypisywanie skryptu to obiektu w grze
Skoro już stworzyliśmy skrypt
generujący planszę wokół jakiegoś obiektu, to nadszedł czas,
aby go do jakiegoś obiektu przypisać. Najpierw stwórzmy jakiś
obiekt w edytorze unity wchodząc w GameObject → Create Empty. Gdy
obiekt jest zaznaczony, w inspektorze wyświetlane są wszystkie jego
komponenty i część właściwości:
Skrypt można do niego dodać na 2
sposoby:
a) Przeciągając skrypt z okna
projektu na przycisk „Add Component”.
b) Wchodząc w Add Component →
Scripts i wybierając utworzony przez nas skrypt.
Po uruchomieniu gry plansza zostanie wygenerowana tam,
gdzie znajduje się utworzony obiekt. Teraz pozostaje tylko ustawić
odpowiednio kamerę i przetestować efekt.
Link do skryptu:
Brak komentarzy:
Prześlij komentarz