Przyspieszony kurs C# pod Unity3d dla leniwych i opornych.

Wiele osób chce zacząć przygodę z tworzeniem gier, ale wiele z nich, nie ma ochoty uczyć się programowania. Jednak nadchodzi ta chwila, kiedy dochodzą do wniosku “Fajnie by było, rozumieć coś z tego kodu”. Czasami zdarza się, że taka osoba jest projektantem, lub grafikiem i nigdy nie potrzebowała nawet oglądać kodu. Innym razem, może nawet chce ogarnąć podstawy programowania, żeby móc coś napisać do swojej gry. Problemem są kursy (lub ich brak) i częste ich znaczne rozbudowanie, gdzie przyszły twórca gier, od razu się zniechęca.

Kurs nie wyczerpuje wszystkich zagadnień związanych z programowaniem w C#. Jego celem jest wyjaśnienie wszystkich podstawowych, oraz najczęściej używanych w pracy z Unity3d zagadnień. Jeżeli oczekujesz zostać programistą C# piszącym aplikacje, ten kurs nie jest dla Ciebie (choć na start może wystarczyć). Jeżeli chcesz zacząć przygodę z Unity3d w bardzo szybkim czasie. Zapraszam!

Forma kursu będzie prosta: Omawiam teorię wspierając się kilkoma przykładami, po czym daje Ci proste zadania do rozwiązania samodzielnego. Jeżeli przebrniesz przez wszystkie bez pomocy z zewnątrz, to znaczy, że jesteś gotowy. Nie oszukuj, podpatrując odpowiedzi, bo na złość robisz tylko sobie. Jeśli natrafisz na problem, przeczytaj jeszcze raz teorię i przeglądnij przykłady. Poradzisz sobie!

Bawić się będziemy w Monodevelop, tak żeby nastawić Cię od razu do pisania pod Unity. Zaczniemy od przygotowania środowiska do pracy.

Środowisko do pracy

Naturalnym środowiskiem do pracy z językiem C# jest Microsoft Visual Studio. Wybór IDE (Integrated Development Environment – Zintegrowane Środowisko Programistyczne – czyli program w którym piszemy i wykonujemy kod), zostawiam wam. Możecie pisać w VS, albo jak ja to będę robił w MonoDevelop z wykorzystaniem Unity3d.

Czym to się różni?

W przypadku VS najbardziej podstawowy kod, będzie wyglądał tak (Podkreślam! Kod jest pod Visual Studio. W Unity3d nie będzie on działał!):

using System;
 
namespace Kurs
{
    class Lekcja_01
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

Program z taką samą funkcjonalnością w Unity, otrzymamy z takiego kodu:

using UnityEngine;
using System.Collections;

public class Lekcja_01 : MonoBehaviour 
{
	void Start () 
	{
		Debug.Log ("Hello World!");
	}
}

Objętościowo różnic dużych nie ma. Istotnymi zmianami są: using UnityEngine; czyli dodanie bibliotek Unity, oraz  : MonoBehaviour  , czyli dziedziczenie po klasie MonoBehaviour, która jest podstawową klasą dla Unity. Pojęcie dziedziczenia będzie omówione później. Na razie nie musisz tego rozumieć. Widoczną różnicą jest jeszcze wypisywanie informacji na konsoli. Dzieje się tak, ponieważ w VisualStudio wypisujemy komunikat na konsolę Windowsa (to czarne okienko, ukryte pod poleceniem “cmd”). W drugim przypadku wypisujemy na wewnętrzną konsolę Unity.
[stextbox id=”info” defcaption=”true”]Import bibliotek na samej górze jest bardzo istotny. Chodzi tu o linijki: using UnityEngine; using System.Collections; Bez nich, kod nie będzie działał. Z racji, że są zawsze, czasami będę podawał kod ograniczony do samej zawartości klasy, żeby bloki kodu były krótsze. Pozwalam sobie je też pomijać, ponieważ przy utworzeniu skryptu, są dodawane automatycznie.[/stextbox]
Różnicę dostrzeżemy też w sposobie uruchamiania kodu. W przypadku VisualStudio, klikamy sobie przycisk “Run” i aplikacja się kompiluje.
[stextbox id=”info” defcaption=”true”]Kompilacja, to proces tłumaczenia kodu programu (tego co napisze programista) na język zrozumiały dla maszyny – czyli komputera.[/stextbox]
W przypadku Unity, będziemy musieli każdorazowo przejść z widoku MonoDevelop do Unity3d i uruchomić grę.

Jeżeli interesuje Cię praca w środowisku VisualStudio, zaglądnij na blog Marka Zająca, gdzie konfiguracja środowiska i uruchomienie programu jest opisane bardzo dobrze. Za to poniżej prezentuję jak będziemy pracować z Unity3d i MonoDevelop.

Jeżeli nie masz jeszcze Unity3d i MonoDevelop, oraz nie wiesz jak poruszać się po samym środowisku Unity, zaglądnij do mojego podstawowego tutoriala.

Przygotowujemy się do pracy z Unity3d i MonoDevelop

Kiedy uruchomisz Unity3d utwórz projekt o dowolnej nazwie. Kiedy pojawi się nowa scena, jedyne co musisz utworzyć to nowy skrypt. (Zapamiętaj tą sekwencję, ponieważ do każdej lekcji będziemy tworzyć nowy skrypt, a nie będę za każdym razem pisał dokładnej instrukcji).

W panelu Project klikamy prawym klawiszem myszki po czym wybieramy Create -> C# Script.

Tworzenie nowego skryptu
Tworzenie nowego skryptu

Od razu po utworzeniu dostajemy szansę nazwania skryptu. Ja swój nazwałem Lekcja_01. Jeżeli omyłkowo odznaczyłeś skrypt przed zmianą nazwy, możesz łatwo wrócić do edycji nazwy pliku przez:

  • Zaznaczenie skryptu i kliknięcie F2
  • Dwukrotne kliknięcie na pliku ale z krótką przerwą między kliknięciami

[stextbox id=”info” defcaption=”true”]Przy nazywaniu skryptu najważniejsze jest, aby nazwa pliku była IDENTYCZNA jak nazwa klasy, która się w nim zajmuje. Istotna jest wielkość znaków. Dodatkowo nazywając plik (i klasę) kieruj się następującymi zasadami:

  • Nazwa może się składać z liter, cyfr i znaków podkreślenia (_) oraz pauzy (-)
  • Pierwszym znakiem w nazwie powinna być wielka litera
  • Nie używaj polskich znaków
  • Stosuje się dwa sposoby nazywania plików:
    • Z pauzą: Czyli_plik_nazwe_tak
    • CamelCase: CzyliPlikNazweTak

[/stextbox]
Dla pewności dodaje obrazek:

Nazwa klasy i pliku muszą być Identyczne!
Nazwa klasy i pliku muszą być Identyczne!

Wynik naszych operacji, będziemy sobie wyświetlać na konsoli. Dlatego trzeba się upewnić, że jest ona dobrze ustawiona. Znajdź panel konsoli w Unity3d i zaznacz wszystkie opcje w prawym górnym rogu, oraz opcję Clear on Play.

Poprawnie ustawiona konsola
Poprawnie ustawiona konsola

Clear on Play sprawi, że przy każdym uruchomieniu konsola zostanie wyczyszczona. Dzięki czemu błędy z poprzedniego uruchomienia nie pomylą się z komunikatami z ponownego. Trzy ikonki po prawej to po kolei od lewej:

  • Informacja – Czyli wynik funkcji Debug.Log,
  • Warrning (Ostrzeżenie) – Czyli ostrzeżenia, narzucają dobre praktyki programowania, ale nie powodują wykolejenia się programu,
  • Error (Błąd) – Czyli błędy krytyczne, które sugerują, że nasz kod gdzieś się posypał i nie działa.

Jeżeli trafisz na jakieś błędy, warto zapoznać się z moim poradnikiem o Debugowaniu, gdzie opisuje najczęściej występujące błędy, oraz to jak sobie z nimi radzić.

Została ostatnia rzecz. Unity3d ma to do siebie, że skrypt żeby był wykonany, musi być przypisany do jakiegoś obiektu w grze. Domyślnie na scenie powinna być kamera (obiekt Main Camera). Przeciągnij swój skrypt na ten obiekt. Jeżeli zrobisz to poprawnie, to po zaznaczeniu obiektu Main Camera, w panelu inspector powinno dać się zobaczyć nasz skrypt.

Poprawnie dołączony skrypt
Poprawnie dołączony skrypt

Teraz kliknij dwukrotnie na swoim skrypcie. Powinien się uruchomić MonoDevelop, a w nim nasz skrypt, który będzie wyglądał następująco:

using UnityEngine;
using System.Collections;

public class Lekcja_01 : MonoBehaviour {

	// Use this for initialization
	void Start () {
	
	}
	
	// Update is called once per frame
	void Update () {
	
	}
}

Start i Update to funkcję Unity. Omawiać ich nie będę, bo przy poznawaniu C# ta wiedza nie jest nam niezbędna. Wystarczy wiedzieć, że Start to funkcja, która wykonuje się zaraz po pojawieniu się skryptu na scenie, a że dodaliśmy go do obiektu, który istnieje na scenie od początku, ostatecznie kod umieszczony w tej funkcji uruchamia się w momencie startu gry.

Zmieniamy ten kod na:

using UnityEngine;
using System.Collections;

public class Lekcja_01 : MonoBehaviour {
	
	void Start () {
		Debug.Log ("Hello World!");
	}
	
}

Wracamy do Unity i uruchamiamy grę, klikając na strzałkę w górnej części interfejsu:

Przycisk startu gry
Przycisk startu gry

Tutaj należy uważać na przycisk pauzy (środkowy), jeśli zostanie włączony, gra zostanie wstrzymana – efektem będzie to, że nawet poprawny skrypt nie wykona akcji. Jeżeli wszystko poszło dobrze, to na konsoli powinno się pojawić coś takiego:

Wynik skryptu
Wynik skryptu

Jeżeli tak jest, wszystko działa poprawnie i możesz przejść do właściwego kursu. Jeśli coś poszło nie tak, upewnij się, że wszystko zostało wykonane poprawnie. Jeśli dalej nie działa, napisz w komentarzu, spróbujemy dociec, co jest nie tak.

Ogólne zasady

Powinienem zacząć od wyjaśnienia, że C# to język obiektowy, o co w tym chodzi i tak dalej i tak dalej. Jednak, póki co zamgliłbym tylko obraz. Dlatego rzucę niezbędne minimum zasad, które musisz zapamiętać:

  • Nazwy zmiennych, funkcji i klas składają się z liter, cyfr i znaku podkreślenia (_) oraz myślnika (-), przy czym cyfra i myślnik nie mogą być pierwsze.
  • Każdą linijkę kodu funkcyjnego kończymy średnikiem (;) – kod funkcyjny, czyli taki, który coś robi (wyjaśni się później)
  • Jak otwierasz nawias, musisz go też zamknąć.

Komentarze. Czyli kod, który nie jest interpretowany przez kompilator. Można w nich wstawiać masakryczne bzdury, ale głównie służą do dokumentowania kodu, czyli opisywania co jest do czego i po co. Komentarze wstawia się dwojako. Albo:

// Moj komentarz

Gdzie wszystko do końca linii od pojawienia się “//” jest wykomentowane, albo:

/* Komentuje
dalej komentuje
łorany boskie jaki komentarz
*/

Jest to blok komentarza. Komentujemy wszystko między /* a */.

Czym są zmienne i stałe?

Zaczynamy od najbardziej podstawowej kwestii. Zmienne i stałe będą twoim przyjacielem cały czas. W sumie bez nich, nie zrobisz nic. Czym one są? Nazwy w sumie wiele mówią. Wyobraź sobie pudełko. Do pudełka możesz coś włożyć. Właśnie tak działają zmienne i stałe, tylko że ze stałej wartości nie możesz wyjąć i zmienić, a w zmiennej wręcz przeciwnie. Jest tylko jeden warunek. Typ tego co wkładasz, musi być zgodny z typem pudełka. Np. do pudełka na pączki, nie włożysz marchewki.

Przykład, który rozjaśni sprawę:

private int nazwaZmiennej = 5;

Lecimy po kolei:

  • private – jest to modyfikator dostępu. Wyróżniamy jeszcze dwa: Public i Protected. Dokładniej, będę je omawiał później, ale ogólnie chodzi o to, czy inne obiekty, mogą korzystać z tej zmiennej czy nie.
  • int – czyli wspomniany typ zmiennej. Int znaczy liczbę całkowitą. Można tutaj wstawić: 5, 3223, 44353 itp. Wstawienie liczby zmiennoprzecinkowej posypie kod.
  • nazwaZmiennej – czyli… nazwa zmiennej. Tutaj wstawiasz dowolne coś, byle zgodne z zasadami i najlepiej żeby tłumaczyło, za co zmienna odpowiada.
  • = 5 – przypisanie zmiennej wartości. Bez tego nasza zmienna przyjmie jedną z domyślnych wartości: 0 dla typów liczbowych (jak int czy float), null dla typów string i GameObject (specyficzny typ w Unity), false dla typu bool oraz pusty znak dla typu char.
  • ; – średnik, bo musi być na końcu linijki.

[stextbox id=”info” defcaption=”true”]Ważna uwaga co do przypisania zmiennej wartości. Wartości domyślne pojawią się, jeśli zmienna jest zmienną dla klasy. Jeżeli zmienną zainicjujemy lokalnie, np. wewnątrz jakiejś funkcji i nie nadamy jej wartości, to kompilator zwróci błąd mówiąc, że zmienna jest niezdefiniowana.[/stextbox]
Jednak jest tu mała podpucha, bo w sumie z tego wszystkiego, wymagane są tylko: typ zmiennej i jej nazwa (no i średnik). Taką deklarację zmiennej jak tu, zobaczycie najczęściej w tym miejscu:

using UnityEngine;
using System.Collections;

public class Lekcja_01 : MonoBehaviour {
	
	// TUTAJ

	void Start () {
		Debug.Log ("Hello World!");
	}
	
}

Jest to wtedy zmienna dla całej klasy. Czyli mamy ją dostępną wszędzie w klasie. Teraz mały pokaz:

Przykład 1

Wprowadź do MonoDevelop taki kod i wykonaj program:

using UnityEngine;
using System.Collections;

public class Lekcja_01 : MonoBehaviour {
	
	private int a = 5;

	void Start () {
		float b = 4.3f;

		for(int i = 0 ; i < 10 ; i++) {
			string c = "Cos";
		}

		Debug.Log("Zmienna a = " + a);
		Debug.Log("Zmienna b = " + b);
	}

	void Update () {
		
	}
	
}

[stextbox id=”info” defcaption=”true”]Konstrukcja: “Zmienna a = ” + a – wartość w cudzysłowie to stały string (czyli łańcuch tekstowy) wyświetli się taki jak go widzimy. Zmiennych używamy przez podanie ich nazwy. Plusem łączymy string ze zmienną. Dzięki temu po uruchomieniu kodu, w konsoli powinno się nam wypisać “Zmienna a = 5”.[/stextbox]
W takim układzie, zmienna a, jest widoczna wszędzie w całej klasie. Zmienna b, jest widoczna tylko w funkcji Start. Zmienna c, jest widoczna tylko wewnątrz pętli for. Zasada jest prosta, zmienna jest widoczna, tylko wewnątrz bloku który ją otacza. Czyli zasięg zmiennej a, wyznaczają wąsiaste nawiasy klasy, dla zmiennej b, robią to nawiasy wąsiaste funkcji start, a dla zmiennej c, są to nawiasy pętli.

Jak widać, w kodzie wykorzystałem 2 inne typu zmiennych: float – czyli liczba zmienno-przecinkowa oraz string – czyli blok tekstu.

Pamiętać należy, że podanie typu zmiennej, jest istotne tylko przy deklarowaniu zmiennej. Jeśli później, chcemy zmienić jej wartość, wystarczy użyć nazwy:

Przykład 2

Wprowadź do MonoDevelop taki kod i wykonaj program:

using UnityEngine;
using System.Collections;

public class Lekcja_01 : MonoBehaviour {
	
	private int a = 5;

	void Start () {
		a = 7;
		Debug.Log ("Zmienna a = " + a);
	}
}

Jako wynik  w konsoli, powinno wyświetlić się: “Zmienna a = 7”

Jeżeli chodzi o stałe, to żeby zmienną zrobić stałą, należy dopisać słówko const w odpowiednim miejscu:

private const string ulubionaStrona = "mwin.pl";

Co nam to daje? Wartość stałej trzeba ustalić przy deklaracji i nie da się jej zmienić w kodzie. Po co to? Jeżeli masz jakąś liczbę, która może ulegać częstej korekcji, a w Twoim kodzie pojawia się ona często, warto ją zmienić na stałą.

Na koniec tablica typów zmiennych. Jest ich więcej, ale daje te najbardziej podstawowe i najczęściej wykorzystywane:

TypReprezentacjaZasięgDomyślna wartość
boolWartość boolowskaTrue or False (Prawda lub Fałsz: 0/1)False
byte8-bitowa liczba całkowita bez znaku (czyli bez ujemnych)0 – 2550
char16-bitowy znak (Unicode)U +0000 to U +ffff‘\0’
double64-bitowa liczba zmiennoprzecinkowa, podwójnej precyzji(+/-)5.0 x 10-324 to (+/-)1.7 x 103080.0D
float32-bitowa liczba zmiennoprzecinkowa-3.4 x 1038 to + 3.4 x 10380.0F
int32-bitowa liczba całkowita-2,147,483,648 to 2,147,483,6470
long64-bitowa liczba całkowita-9,223,372,036,854,775,808 to 9,223,372,036,854,775,8070L
stringCiąg znaków“\0”

Na koniec tego tematu mały zabawny przykład:

Przykład 3

Wprowadź do MonoDevelop taki kod i wykonaj program:

using UnityEngine;
using System.Collections;

public class Lekcja_01 : MonoBehaviour {

	int a = 2147483647;

	void Start () {
		a++;
		Debug.Log (a);
	}
	
}

Najpierw wyjaśnię kod: a++. Jest to zwykła inkrementacja (zwiększenie  o 1). Znaczy tyle samo co: a = a + 1. Można tak samo zrobić z minusem.

Ale wróćmy do wyniku operacji. Czego się spodziewamy? 2147483648? A co dostaliśmy? -2147483648. Dlaczego minus? Bo przeładowaliśmy pamięć zmiennej. Po prostu typ int nie był w stanie zmieścić więcej i się przekręcił na swój początek licznika.

Operatory (Matematyka)

Dobra, może będzie brutalnie, ale zakładam, że każdy kto to czyta, skończył 3 klasę szkoły podstawowej i wszystko mu się rozjaśni, jeśli powiem że operatory to np,: dodawanie (+) czy mnożenie (*). W nawiasie jest Matematyka, bo istnieją jeszcze operatory logiczne, ale je omówię kiedy indziej.

Podstawowe operatory matematyczne w c# to:

  • + [Dodawanie]
  • – [Odejmowanie]
  • * [Mnożenie]
  • / [Dzielenie]
  • % [Dzielenie modulo – czyli reszta z dzielenia. Np.: 4 % 2 = 0 (bo w czwórce, dwójka mieści się dwa razy i nic nie zostaje – 4 = 2 * 2), 7 % 2 = 1 (bo 7 = 3 * 2 + 1)

A jak to stosować? Tak jak na kartce papieru:

Przykład 4

Wprowadź do MonoDevelop taki kod i wykonaj program:

using UnityEngine;
using System.Collections;

public class Lekcja_01 : MonoBehaviour {
	
	private int a = 5;

	void Start () {
		int b = 7;
		int c = 10;
		Debug.Log (a + b);
		Debug.Log (c);

		c = a * b;
		Debug.Log (c);
	}
}

W konsoli powinno się wyświetlić: 12, 10 i 35.

W jednej linii można wykonać więcej operacji na raz i stosować nawiasy do segregowania operacji:

Przykład 5

Wprowadź do MonoDevelop taki kod i wykonaj program:

using UnityEngine;
using System.Collections;

public class Lekcja_01 : MonoBehaviour {

	void Start () {
		int a = 2 + 2 * 2;
		int b = (2 + 2) * 2;
		Debug.Log (a + " - " + b);
	}
	
}

Przykład ten pokazuje nie tylko to o czym pisałem, ale też, że Unity zna kolejność wykonywania działań. Na konsoli powinno wyjść: “6 – 8”

Przydać się może jeszcze potęga czy pierwiastek. Tutaj nie jest tak łatwo, bo potrzeba biblioteki matematycznej. Robi się to tak:

using UnityEngine;
using System.Collections;

public class Lekcja_01 : MonoBehaviour {
	void Start () {
		Debug.Log (Mathf.Pow (2, 5));
	}
}

Trzeba korzystać z funkcji. Pow to potęga, Sqrt to pieriwastek. Tak samo, możecie dostać się do funkcji trygonometrycznych (sin, cos, tan, tan2). Oraz do wartości bezwzględnej (abs).

Przy czym warto z tym uważać, bo ogólnie potęgowanie do 2 potęgi dla procesora jest szybsze w postaci: a * a niż: Mathf.Pow(a, 2). Czasami grę może przyspieszyć rozpisanie wysokiej potęgi do postaci ciągu mnożeń.

Jeżeli chodzi o operacje matematyczne, możemy je czasami zapisać skrótowo:

a = a + 1;
a += 1;
a++;

Wszystkie te linijki robią to samo. Zwiększają wartość zmiennej a o 1.

b = b * 3;
b *= 3;

Tutaj tylko pokazuję, że da się z innym działaniem i inną liczbą niż jeden.

Zadanie domowe

OK, nie zrobiliśmy tutaj za dużo, bo w sumie ruszyliśmy jedynie operatory (bo wstęp był długi) więc zadania nie będzie. Mógłbym tylko oczekiwać policzenia czegoś. Warto sobie tutaj po testować różne kombinacje.

Kolejne lekcje – Spis Treści

Następna Lekcja -> Funkcja warunkowa (If, Swith-Case), operatory logiczne