Programowanie C# W Unity
Programowanie C# W Unity
PROGRAMOWANIE C#
W UNITY
Wersja 1.0
PIKADEMIA
Skrypty w Unity
Każdy skrypt w Unity tworzy nową klasę.
Nazwy skryptów są jednocześnie nazwą klasy i powinny być tworzone według notacji PascalCase.
• Nazwę klasy zaczynamy z wielkiej litery jak i każde słowo składowe.
• Należy używać tylko liter alfabetu angielskiego (bez polskich znaków)
• Nie wolno używać spacji
• Nie wolno rozpocząć nazwy od cyfry
• Nazwy powinny być deskryptywne (opisowe)
2
Metoda Start()
Pamiętaj!
Metoda Start() wykonuje się tylko 1 raz w trakcie “życia” danego skryptu w grze
void Start()
{
[Link]("Start!");
}
Metoda Start() wykona się automatycznie tuż po tym jak dany skrypt (lub obiekt z tym
skryptem) zostanie załadowany i aktywowany na scenie.
3
Metoda Update()
Pamiętaj!
Metoda Update() wykonuje się wiele razy w ciągu każdej sekundy. Ilość wywołań jest
zmienna, zależy od mocy komputera i złożoności wyświetlanej sceny.
void Update()
{
[Link]([Link]);
}
Metoda Update() zacznie wykonywać się automatycznie tuż po tym jak dany skrypt
(lub obiekt z tym skryptem) zostanie załadowany i aktywowany na scenie oraz po tym
jak funkcja Start() zakończy swoje działanie.
4
Zawartość skryptu - class
public class DebugowanieWKonsoli : MonoBehaviour
{
// zawartość klasy
}
6
[Link]()
Metoda [Link]() służy do szybkiego wyświetlania w konsoli informacji związanych z grą w środowisku
produkcyjnym.
Wewnątrz nawiasu, jako argument można podać wiele typów informacji, które mają być wyświetlone.
Przykłady:
Wyświetlenie wyniku działania matematycznego:
[Link](12 * 45);
7
Liczby całkowite int
Język C# rozróżnia wiele typów danych, np.: liczby całkowite, liczby ułamkowe, znaki, tekst, wartości logiczne i setki innych.
Każda nowoutworzona klasa definiuje nowy typ danych.
Istnieje wiele typów, które reprezentują liczby całkowite, różnią się one wielkością zajmowanej pamięci, a co za tym idzie maksymalnym
zakresem liczb, które można zapisać za pomocą takiego typu. Podstawowym i najczęściej używanym typem jest integer, czyli int.
Zajmuje on 4 bajty pamięci (32bity) i może przechować liczby w zakresie od -2 147 483 648 do 2 147 483 647
Innym bardzo często używanym typem jest byte, który przechowuje tylko liczby dodatnie od 0 do 255. Używa się go bardzo często do
zapisu wartości składowych kanałów koloru.
8
Zmienne
Zmienna to taki wirtulny kontener, który może przechować informacje danego typu. Podczas tworzenia zmiennej deklarujemy typ danych
jaki będzie mógł być w niej zapisany oraz unikalną nazwę, za pomocą której będziemy daną zmienną wywoływać w dalszej części kodu.
W dalszej części programu możemy przypisywać i nadpisywać wartości tych zmiennych za pomocą ich nazw, np.:
wiek = 12;
tempCiala = 36.6f;
Możemy też od razu przypisać wartości do zmiennych w czasie ich tworzenia i wtedy nazywamy to inicjalizacją, np.:
long odlegloscDoInnejGalaktyki = 1267865421;
9
Liczby zmiennoprzecinkowe float
Liczby zmiennoprzecinkowe to inaczej liczby ułamkowe.
Mamy do dyspozycji 3 podstawowe typy: float, double i decimal.
[Link]
Typ float jest najczęściej używany w środowisku Unity, zajmuje 32 bity pamięci i wg dokumentacji jego precyzja wynosi od 6-9 cyfr.
W Unity precyzja ta wynosi 7 cyfr.
Typy zmiennoprzecinkowe mogą przechowywać bardzo duże liczby, ale z małą precyzją. Co to oznacza?
Np. liczba 72 678 942 będzie zaokrąglona do 7 cyfr znaczących i zapisana w postaci wykładniczej jako:
7.267894E+07
Oznacza to, że liczbę 7.267894 mnożymy przez 107 czyli przesuwamy przecinek o 7 pozycji w prawo. Ostatecznie dostajemy liczbę:
72 678 940
Jak widzisz, liczbę 72 678 942 zapisano jako 72 678 940, została ona zaokrąglona z uwzględnieniem 7 cyfrowej precyzji.
Przykładowo liczba 23 879 234 657 zostanie zapisana za pomocą typu float jako 2.387924E+10, czyli 23 879 240 000.
Za pomocą typu float możemy zapisać bardzo duże liczby, ale z ograniczoną precyzją.
W większości przypadków większa precyzja nie jest wymagana, ale jeśli zajdzie taka potrzeba należy użyć typu double lub decimal.
10
Wartości logiczne bool
Wartości logiczne boolean mogą przyjmować wartość true lub false.
Na ich podstawie działają komputery, które zbudowane są z milionów tranzystorów, które mają właśnie 2 stany 1 i 0, czyli prawda i fałsz.
Cały system opiera się na przełączaniu tych dwóch stanów na milionach mikroskopowych przekaźników.
Wiele metod w Unity zwraca właśnie wartość typu bool i na ich podstawie będziemy mogli dokonywać odpowiednich decyzji w grze.
Zazwyczaj wartości typu bool wykorzystywane są z instrukcjami warunkowymi, które poznamy w dalszej części kursu.
11
Wartości tekstowe string
Wartości tekstowe w programowaniu zapisywane są w zmiennych typu string i zawsze powinny być zawarte wewnątrz cudzysłowu, np.:
string imie = "Angelika";
[Link]
Typ string różni się od typów prostych takich jak: int, float, bool czy char.
Wielkość pamięci zajmowanej przez zmienną typu string zależy od jej zawartości, czyli ilości znaków zapisanych wewnątrz niej.
W dużym uproszczeniu można powiedzieć, że zmienna typu string, która zawiera 5 znaków Unicode będzie zajmowała 5x2=10 bajtów
pamięci. Zmienna imie z przykładu powyżej będzie zajmowała ok. 8x2=16 bajtów pamięci.
Typ string w języku C# jest immutable, to znaczy, że nie można zmienić zawartości takiej zmiennej w taki sam sposób jak to miało miejsce
w przypadku typów prostych. Co prawda dla nas proces nadpisywania wygląda tak samo jak w przypadku typów prostych, to jednak za
kulisami proces ten wygląda zupełnie inaczej.
Stringi są obiektami, a zmienna przechowuje w sobie tylko adres do tej wartości. W przypadku nadpisania takiej zmiennej, w pamięci
komputera tworzony jest nowy obiekt z nową wartością, a w zmiennej podmieniony zostaje tylko adres do tego nowego miejsca w
pamięci komputera. Stara wartość zostanie z czasem usunięta przez specjalne narzędzie zwane Garbage Collector (GC).
12
Inkrementacja
Inkrementacja lub dekrementacja to zwiększenie lub zmniejszenie zmiennej o daną wartość.
Istnieją 3 sposoby zastosowania inkrementacji / dekrementacji:
int counter = 0;
counter = counter + 1; Istnieją 2 rodzaje inkrementacji:
counter += 1;
counter++; int counter = 0;
counter++; //postinkrementacja
Najkrótszej wersji counter++ stosuje się tylko do zmiany wartości o 1. ++counter; // preinkrementacja
W przypadku, gdy chcemy dostosować zmienną o inną wartość niż 1
lub za pomocą innej operacji niż +/-,
najczęsciej będziemy stosować formę skróconą, czyli: counter += 3;
13
Kolejność wykonywania operacji
Category Operator Associativity
Postfix () [] -> . ++ -- Left to right
Unary + - ! ~ ++ - - (type)* & sizeof Right to left
Multiplicative */% Left to right
Additive +- Left to right
Relational < <= => > Left to right
Equality == != Left to right
Logical AND && Left to right
Logical OR || Left to right
Conditional ?: Right to left
Assignment = += -= *= /= %= Right to left
Comma , Left to right
14
Tablice
Tablice służą do przechowywania stałej ilości elementów o takim samym typie danych. W celu przechowania 3
liczb całkowitych możemy stworzyć tablicę o typie int, podając liczbę elementów, które znajdą się w niej, np.:
Sortowanie listy
[Link]();
[Link]();
16
Pętla foreach
Pętla pozwala wykonywać powtarzalne instrukcje dopóki podany warunek jest spełniony. Mamy do dyspozycji 4 pętle: foreach, for,
while i do while. Pętla foreach jest używana wraz z kolekcjami (tablicami, listami, wartościami typu string).
int i = 5; zmienna_iteracyjna;
while(i <= 20) while(warunek_wykonania_pętli)
{ {
[Link](i); //instrukcje;
i++; skok;
} }
W celu zmiany właściwości lub wyzwolenia funkcji dostępnych dla danego komponentu należy odnieść się do niego w skrypcie za pomocą
GetComponent<Nazwa Komponentu>().
20
Komponenty - cache
W przypadku, gdy do danego komponentu będziemy chcieli odnosić się wielokrotnie, należy go przypisać do zmiennej (cache), aby
ustalenie tego połączenia nastąpiło tylko raz, co wpływa pozytywnie na wydajność programu.
Komponenty cache’ujemy (czyt. kaszujemy) przypisując je do zmiennej o tym samym typie, co sam komponent (ponieważ komponent
jest klasą, a klasa definiuje typy w programowaniu).
SpriteRenderer spriteRenderer;
void Awake()
{
spriteRenderer = GetComponent<SpriteRenderer>();
}
void Start()
{
[Link] = [Link];
}
Dzięki takiemu podejściu w dalszej części programu możemy odnosić się do danego komponentu za pomocą zmiennej spriteRenderer.
W większości przypadków komponenty cache’ujemy w metodzie Awake() choć w niektórych przypadkach, możemy to również zrobić w
metodzie Start().
21
new Vector3(float x, float y, float z)
W Unity bardzo często korzysta się z wektorów, które są zestawem wartości x, y, z.
Wektory są obiektami, więc za każdym razem, kiedy będziemy je inicjalizować, użyjemy słowa kluczowego new.
Składowe wektorów są wartościami typu float.
Wektory mogą określać kierunek, pozycję, skalę i inne właściwości,
które wymagają 3 cech składowych.
Chcąc teraz ustawić nową pozycję określoną powyższym wektorem możemy użyć instrukcji:
[Link] = newPosition;
W przykładzie poniżej zmienimy skalę obiektu, która będzie różna dla każdej z trzech wartości:
[Link] = new Vector3(2f, 3f, 4f);
Zapis powyżej zwiększy szerokość obiektu 2x, wysokość 3x i głębokość 4x.
22
Input – Kliknięcia myszy
Do sprawdzania interakcji użytkownika za pomocą myszy możemy skorzystać z wielu metod klasy Input, np:
GetMouseButton, GetButton, GetKey.
[Link](0);
Zwraca wartość bool true, kiedy określony przycisk myszy jest wciśnięty
Lista argumentów:
[Link](0);
Zwraca jednorazowo wartość true, w klatce, w której wykryto wciśnięcie przycisku 0 / Fire1 / Mouse0 – LMB (Left)
1 / Fire2 / Mouse1 – RMB (Right)
[Link](0);
Zwraca jednorazowo wartość true, w klatce, w której wykryto zwolnienie przycisku 2 / Fire3 / Mouse2 – MMB (Middle)
23
Input – GetAxis
Klasa Input zawiera metodę, dzięki której możemy odczytywać wartości osi joysticku lub klawiszy WSAD.
Klawisze AD i strzałki Left/Right będziemy traktować jako oś poziomą X
Klawisze WS i strzałki Up/Down będziemy traktować jako oś pionową Y
W przypadku ruchu pod kątem mamy do czynienia z dodawaniem składowych X i Y, co objawia się większą prędkością
poruszania. W celu zniwelowania tego efektu, należy taki wektor znormalizować.
void Update()
{
osX = [Link]("Horizontal");
osY = [Link]("Vertical");
[Link](new Vector3(osX, osY, 0).normalized*speed*[Link]);
}
24
Losowa liczba
Rozgrywka byłaby nudna, gdyby zawsze wyglądała w dokładnie taki sam sposób. Za pomocą losowości można uatrakcyjnić
tworzoną grę. Język C# posiada klasę Random, która znajduje się w przestrzeni nazw – System. Biblioteka UnityEngine też
posiada osobną klasę Random i to właśnie jej będziemy używać do tego celu.
Możemy stworzyć losowe liczby typu int i float.
W przypadku typu integer, wartość minimalna jest inclusive, a maksymalna exclusive. Oznacza to, że wartość maksymalna
nigdy nie zostanie wylosowana. Chcąć ustalić zakres losowania, należy wartość maksymalną zwiększyć o 1.
W przypadku typu float, zarówno min i max są wartościami inclusive, oznacza to, że wylosowana liczba będzie z pełnego
zakresu.
25
Spawnowanie
Możemy utworzyć obiekt w wybranym przez nas miejscu i o określonej rotacji za pomocą odpowiedniego przeciążenia metody
Instantiate()
26
Spawnowanie
27
Input – Pozycja myszy na ekranie
Sprawdzanie pozycji x kursora myszy na ekranie
[Link]([Link].x / [Link]);
void Update()
{
if ([Link](0))
{
Vector3 mousePos = [Link]([Link]);
mousePos.z = 0f;
Instantiate(prefab, mousePos, [Link]);
}
}
28
Rotacja względem kursora myszy
Camera cam;
void Start()
{
cam = [Link];
}
void Update()
{
Vector3 mousePos = [Link]([Link]);
Vector3 diff = mousePos - [Link];
[Link]();
float rotZ = Mathf.Atan2(diff.y, diff.x)*Mathf.Rad2Deg;
[Link] = [Link](0f, 0f, [Link](rotZ, -6f,90f));
29
Input – OnMouseDrag
Droppable object
Vector3 GetMousePos(){
Vector3 mousePos = [Link]([Link]);
mousePos.z = 0f;
return mousePos;
}
30
Najpotrzebniejsze skróty w Visual Studio
31
Player movement rb
public class PlayerMove : MonoBehaviour
{
float dirX;
float speedX = 10f;
Rigidbody2D rb;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
dirX = [Link]("Horizontal");
}
32
Player jump rb
33
Pocisk
void Start()
{
rb = GetComponent<Rigidbody2D>();
[Link] = [Link] * speed;
Destroy(gameObject, 2f);
}
}
34
Strzelanie
void Update()
{
if ([Link](0))
{
Shoot();
}
}
void Shoot()
{
GameObject bullet = Instantiate(prefab, [Link], [Link]);
}
}
35
Hp
public class Bullet : MonoBehaviour public class Hp : MonoBehaviour
{ {
Rigidbody2D rb; [SerializeField] int maxHp = 10;
[SerializeField] float speed = 50f; int hp;
[SerializeField] int attack = 1;
void Start()
void Start() {
{ hp = maxHp;
rb = GetComponent<Rigidbody2D>(); }
[Link] = [Link] * speed;
Destroy(gameObject, 2f); public void TakeDamage(int damage)
} {
hp -= damage;
private void OnCollisionEnter2D(Collision2D collision) if(hp <= 0)
{
{
Hp hp = [Link]<Hp>();
if(hp != null) Destroy(gameObject);
{ }
[Link](attack); }
} }
Destroy(gameObject);
}
}
36