Skrypty Unity to ważny element tworzenia własnej gry. Krok po kroku wyjaśniamy zasady pisania skryptów w języku C#.
Chociaż Unity3D pozwala na wykonanie wielu czynności tylko za pomocą paru kliknięć myszą, to jeżeli myślimy o stworzeniu czegoś bardziej ambitnego niż banalna pseudoplatformówka pozwalająca na bieganie wokół drzewa, nie obejdzie się bez pisania własnych skryptów. Ręczne „kodzenie” gwarantuje praktycznie nieograniczone możliwości, gdyż możemy zlecić środowisku dowolnie wymyślone przez nas czynności, które nie zostały domyślnie zdefiniowane. I wcale nie jest to tak nieludzko trudne, jak wywnioskować można z filmów o hakerach. Programowanie to nie „klepanie zer i jedynek”, wbrew pozorom jest bardzo spójne logicznie i względnie proste do nauczenia, wystarczy trzymać się określonych zasad.
W dalszych kilku częściach poradnika postaram się przeprowadzić niewielki kurs w pigułce dla osób, które nigdy z programowaniem styczności nie mieli. Zrozumienie tej części jest bardzo ważne, bez tego nie ruszymy z miejsca. Poza generowaniem mapy, każdy kolejny odcinek serii będzie okraszony odpowiednią dawką „kodzenia„, więc przestrzegam – przyłóżmy się do lekcji :).
Unity pozwala na skryptowanie w dwóch językach: JavaScript oraz C#. My będziemy używać tego drugiego. Dlaczego? Osobiście uważam jego składnię za czystszą, łatwiejszą do zrozumienia i poukładania, więc i nauka powinna być prostsza. Teoretycznie.
Jak pisać Skrypty Unity w C#
Rzućmy okiem na dwa kody. Z lewej widzimy najprostszy możliwy kod w języku C#, którego zadaniem jest wypisanie na ekran konsoli systemowej zdania: „Witaj świecie!”. Drugi robi dokładnie to samo.. tylko dla środowiska Unity. Wersja „konsolowa”, z lewej strony jest czystym C#, z którego korzystalibyśmy pisząc aplikacje np. w Visual Studio. My natomiast używać będziemy tej drugiej w czasie pracy nad grą.
Zacznijmy od podstaw, co tutaj widać?
Na samej górze znajdują się importy bibliotek.
Bibliotekami nazywamy konkretne zestawy narzędzi, które należy załączyć do programu gdy chcemy ich używać. Jest to niezbędne gdyż:
- po pierwsze – ze względu na to, że nie zawsze chcemy korzystać z niektórych funkcjonalności i wtedy nie ma potrzeby dołączania ich do programu;
- po drugie – użytkownicy mogą tworzyć własne zestawy przydatnych narzędzi, które można wymieniać między programami. Wtedy jesteśmy w stanie korzystać z nich w przypadku różnych projektów, wystarczy je odpowiednio podpiąć;
- po trzecie – funkcjonalności w różnych bibliotekach mogą mieć te same nazwy. W ten sposób możemy je rozróżnić. Wiemy, z których konkretnie bibliotek pochodzą. Gdyby biblioteki nie istniały, nie wiedzielibyśmy czy dana funkcjonalność jest tą, z której chcemy skorzystać, czy może zbiegiem okoliczności ma jedynie taką samą nazwę.
Bibliotekę w języku C# podpinamy za pomocą słówka kluczowego using oraz jej nazwy.
Przykład:
Powoduje załączenie biblioteki systemowej, która w przypadku przykładu numer 1 pozwala na użycie metody Console.WriteLine, wypisującej tekst na ekran konsoli (czarnego okienka systemowego).
W dokładnie ten sam sposób załączenie biblioteki UnityEngine umożliwia nam skorzystanie z metody Debug.Log w przykładzie dla Unity, co z kolei spowoduje wypisanie tekstu na ekran konsoli, ale tej wbudowanej w środowisko Unity.
Często zdarza się tak, że w daną bibliotekę upakowane są jeszcze inne „podbiblioteki”, a z kolei w tamte jeszcze inne i tak dalej. Jest to bardzo przyjemny sposób na zorganizowanie funkcjonalności, które można w ten sposób dzielić na grupy i podgrupy, od ogółu do szczegółu.
Chcąc dostać się do konkretnej podbiblioteki, przechodzimy kolejno poziomami od ogółu do szczegółu, oddzielając je kropkami.
Załóżmy, że posiadamy bibliotekę Weterynarz. W jej skład wchodzić będą podbiblioteki Leczenie i Pielegnacja. Z kolei do Pielegnacji zaliczymy bibliotekę zawierającą funkcjonalności związane z Myciem, Czesaniem i ObcinaniemPazurow (zwróćmy uwagę na nazewnictwo, nie oddziela się wyrazów spacją, lecz każdy kolejny pisze się ciągiem, zaczynając wielką literą. przykład: ToJestMojaNowaBiblioteka. Nie używamy również polskich znaków w kodzie).
Hierarchia wygląda więc mniej więcej tak:
- Weterynarz - Leczenie - ... - ... - Pielegnacja - Mycie - Czesanie - ObcinaniePazurow
Aby dostać się do konkretnej biblioteki, np. Czesania trzeba by ją zaimportować w następujący sposób:
using Weterynarz.Pielegnacja.Czesanie;
Jeżeli chcielibyśmy zaimportować zarówno Mycie, Czesanie jak i ObcinaniePazorow wystarczy zapisać to w ten sposób:
using Weterynarz.Pielegnacja;
Wtedy załączona zostanie cała zawartość biblioteki Pielęgnacja.
Co wyjaśnia prawdopodobnie niezrozumiały do tej pory zapis z programów Przykład1 i Przykład2, gdzie w jednym użyte było: using System;
natomiast w drugim: using System.Collections;
Staramy się zawsze importować jak najmniej, tylko tyle, ile jest nam potrzebne.
Odpowiednie domykanie linii.
Każdą kolejną linię kodu należy w odpowiedni sposób domknąć. Dokonujemy tego za pomocą średnika lub klamerek. Kiedy używamy czego? Najprościej jest rozróżnić to w ten sposób: średnikiem domykamy linię, która nie deklaruje żadnej wewnętrznej funkcjonalności – np. importowanie bibliotek. Załączamy bibliotekę i na tym koniec, stawiamy średnik – sprawa zamknięta.
Inaczej ma się to w przypadku, kiedy dana linia definiuje jakąś wewnętrzną funkcjonalność. Stawiamy wtedy klamrę otwierającą, wypisujemy wszystko co ma się wydarzyć po wywołaniu tej linii, a całość zamykamy klamrą zamykającą. Takie zachowanie jest charakterystyczne w przypadku wszelakich metod, klas, instrukcji warunkowych czy pętli. Doskonale wiem, że wydaje się to kompletnie niezrozumiałe. Te wszystkie wymienione przeze mnie wyrażenia omówimy sobie w kolejnych częściach. Na ten moment najlepiej będzie zapamiętać, że klamry w programowaniu są podobne do nawiasów w matematyce – spajają ze sobą część kodu, która przynależeć będzie niejako pod instrukcję po której występują, oznaczają zwyczajnie początek i koniec danej procedury.
Klasy i ich nazewnictwo.
Czym jest klasa i o ogólnie szeroko pojętym programowaniu obiektowym również opowiem w następnych częściach. Na dzień dzisiejszy zapamiętajmy – każdy skrypt, czyli każdy plik posiadać musi klasę. I nie chodzi mi wcale o to, że musi on być dobrze ubrany :). Klasa jest głównym elementem każdego skryptu, to w niej definiowane są wszystkie dane i funkcjonalności. Jest idealnym przykładem na użycie wyżej wspomnianych klamer, które spajają wszystko, co do danej klasy przynależy (oznaczone na żółto, zrzut ekranu poniżej).
Klasę definiujemy za pomocą słówka class i jej Nazwy. W przykładach występuje jeszcze przed tym specyfikator dostępu public, o specyfikatorach opowiemy sobie, a jakże, w następnych częściach. Na teraz zapamiętajmy, że w jednym pliku wystąpić może wiele klas, ale tylko jedna z nich może zostać oznaczona jako publiczna (public). Taka klasa jest główną klasą i musi mieć nazwę identyczną jak nazwa pliku. A raczej to plik powinien przejąć nazwę klasy. Dlatego pisząc skrypt kontrolujący mruczenie kota główną klasę oznaczamy:
public class MruczenieKota{}
A plik MUSI nazywać się wtedy MruczenieKota.cs. Nie jest to jedynie przyjęty sposób nazewnictwa dla ułatwienia, ale wymóg. [.cs] jest rozszerzeniem plików pisanych w języku C#.
Bardziej dociekliwych zastanawiać może dlaczego w przykładzie dla unity po nazwie klasy występuje dwukropek i słówko MonoBehaviour. W ten sposób oznaczamy dziedziczenie. Klasy potrafią dziedziczyć po innych klasach przejmując ich funkcjonalności, zupełnie jak dziedziczenie w realnym świecie. W Unity wszystkie główne klasy skryptów, klasy publiczne, dziedziczą po MonoBehaviour. Więcej o dziedziczeniu napiszę w kolejnych częściach, bo to wystarczająca porcja wiadomości do przyswojenia na jeden raz.
Przejdź do poradnika Unity3D: