sobota, 12 marca 2016

Stawianie żetonów na planszy

Plan na dziś jest prosty: sprawić, że po kliknięciu na pole pojawi się na nim żeton. Na razie pominiemy możliwość wybrania żetonu i związane z nim animacje – po prostu chcemy postawić żeton.


Wydarzenie wyzwalające

Dotychczas wszystkie czynności programu zaczynały się na jego starcie i nawet można było trzymać cały kod w jednym skrypcie. Jednak teraz idziemy o krok naprzód i chcemy aby żeton tworzył się po kliknięciu na pole, co może zdarzyć się w dowolnym momencie gry. Unity oferuje nam funkcję „OnMouseOver ()”, która aktywuje się za każdym razem, gdy najedziemy myszką na obiekt posiadający skrypt. Pamiętamy jednak, że w grze będziemy mieli do wyboru 36 różnych pól, a więc wygląda na to, że skrypt będzie musiał być przypisany do każdego z nich. Dodatkowo nie możemy przypisać do pól wcześniej utworzonych skryptów, ponieważ miały one uruchomić się jednorazowo przy starcie gry, a jeśli przypiszemy je kilkudziesięciu pól, to aktywują się kilkadziesiąt razy. Dlatego też tworzymy nowy skrypt, który przypiszemy potem do pól.

Najechanie myszką na pole jest zdarzeniem wyzwalającym aktywację kodu, ale potrzebny jest jeszcze jeden warunek: wciśnięty lewy przycisk myszy. Unity oferuje nam 3 wbudowane funkcje typu zmiennej bool:

 Input.GetMouseButton (0) // zwraca prawdę, jeśli przycisk myszy jest przytrzymany
 Input.GetMouseButtonDown (0) // zwraca prawdę, jeśli przycisk myszy został wciśnięty
 Input.GetMouseButtonUp (0) // zwraca prawdę, jeśli przycisk myszy został zwolniony

Pierwszą funkcję tutaj zdecydowanie omijamy, ponieważ przytrzymanie lpm sprawi, że funkcja aktywuje się tyle razy, ile klatek będzie trwało przytrzymanie lpm, a my chcemy, aby aktywowała się tylko raz. Różnica między 2 i 3 funkcją ma spore znaczenie tylko wtedy, gdy chcemy umożliwić przeciąganie obiektów/rozkazów pomiędzy dwoma punktami, więc póki co wybór nie ma tutaj dużego znaczenia. Gdybyśmy chcieli, aby funkcja aktywowała się dla ppm, to wtedy jako argument funkcji wstawiamy 1, a w przypadku środkowego przycisku myszy argumentem funkcji powinna być liczba 2.

Na razie więc funkcja wygląda tak:

 void OnMouseOver () {
  if (Input.GetMouseButtonDown (0)) {
  }
 }

I w środku tej funkcji powinniśmy wstawić skrypt tworzący żeton.

Komunikacja między skryptami

W trakcie tworzenia żetonu chcemy korzystać z danych dotyczących tego żetonu, które znajdują się w innym skrypcie. I tutaj mamy dwie główne możliwości:
a) Pobieramy dane żetonu ze skryptu z danymi do skryptu sterującego.
b) Pobieramy informację o tworzeniu żetonu ze skryptu sterującego do skryptu z danymi.
Danych żetonu może być wiele, natomiast informacja o postawieniu żetonu jest jedna, dlatego wygodniej będzie skorzystać z drugiej opcji. Fragment kodu tworzący żeton umieścimy więc w naszym głównym skrypcie i użyjemy do tego funkcji typu IEnumerator, którą możemy pauzować na dowolną ilość czasu, co przyda nam się później np. do opóźniania efektów graficznych umiejętności. Dla funkcji tej stworzymy dwa argumenty: współrzędną x pola, oraz współrzędną y pola. Funkcja będzie więc wyglądała tak:

 public IEnumerator CreateToken (int x, int y) {
 }

Funkcja ta jest zmienną publiczną, abyśmy mogli się do niej dostać z poziomu innych funkcji. Wróćmy więc do funkcji sterującej, aby dodać tam wywołanie tej funkcji. Funkcję typu IEnumerator
można wywołać komendą StartCoroutine () której argumentem będzie wspomniana funkcja.

Jeśli odwołujemy się to funkcji z innego skryptu, to najpierw musimy odwołać się do jej skryptu, a aby odwołać się do skryptu, musimy wpierw odwołać się do obiektu w którym on się znajduje. Wszystko tutaj dzieje się według reguły „od ogółu do szczegółu”. Z pomocą przychodzi nam funkcja GameObject.Find (), która zwraca obiekt znajdujący się na scenie o nazwie podanej jako argument tej funkcji. Skoro mamy dostęp obiektu to teraz możemy odwołać się do jego komponentu za pomocą funkcji GetComponent<> (), która zwraca komponent o nazwie znajdującej się w nawiasie ostrokątnym, gdzie podamy nazwę naszego skryptu. Jeśli założymy, że skrypt ma nazwę „MScript” i znajduje się w obiekcie „MObject”, to aktywacja naszej funkcji CreateToken () będzie wyglądała następująco:

 StartCoroutine (GameObject.Find ("MObject").GetComponent<MScript> ().CreateToken (x, y));

Jak już wspomniałem, funkcja potrzebuje 3 argumentów, więc w klasie tego skryptu trzeba utworzyć zmienne które będą zawierały potrzebne informacje:

 public int x;
 public int y;

Na razie te zmienne są puste.

Przypisywanie skryptu do pól

Skoro pola na planszy były całkowicie generowane za pomocą głównego skryptu, to i nowy skrypt powinien być dodany w trakcie generowania tych pól. Cofamy się więc i modyfikujemy fragment kodu odpowiedzialny za pola, aby wyglądał następująco:

 for (int x = 0; x < 6; x++) {
  for (int y = 0; y < 6; y++) {
   Clone = GameObject.CreatePrimitive (PrimitiveType.Quad);
   Clone.transform.parent = transform;
   Clone.transform.localPosition = new Vector3 (-2.5f + x, -1.5f + y, -0.01f);
   Clone.transform.localScale = new Vector3 (0.9f, 0.9f, 1);
   Clone.GetComponent<Renderer> ().material.color = new Color (0.3f, 0.3f, 0.3f);
   Clone.AddComponent<ControlScript> ();
   Clone.GetComponent<ControlScript> ().x = x;
   Clone.GetComponent<ControlScript> ().y = y;
   Field [x, y] = Clone;
  }
 }

Zmiany niewielkie: dodano 4 linijki kodu, przy czym 3 pierwsze odpowiedzialne są za skrypt który nazwaliśmy „ControlScript”. Pierwsza linijka dodaje ten skrypt do pola jako jego komponent, natomiast dwie kolejne linijki odwołują się do tego komponentu, by móc zmodyfikować znajdujące się w nim zmienne i ustalić dla nich odpowiednią wartość. Zmienna PNumber pozostaje póki co nienaruszona. Ostatnia linijka odpowiada za przypisanie obiektu to tablicy zmiennych Field, którą dodatkowo musimy zadeklarować w naszej klasie:
 public GameObject [,] Field = new GameObject [6, 6];
Dzięki tej zmiennej w dowolnym momencie będziemy mogli w łatwy sposób odwołać się do dowolnego pola na planszy.

Tworzenie żetonu

Skoro mamy już przygotowane wszystkie dane związane z wywołaniem funkcji tworzącej żeton, to możemy przejść do samego tworzenia żetonu, co dokonamy za pomocą instancji. Najpierw utwórzmy w assetach folder „Resources” do którego możemy łatwo odwołać się z poziomu kodu, a następnie utwórzmy w nim prefab o nazwie „PreToken”, w którym umieścimy nasz żeton. Może on być złożonym modelem 3D, ale równie dobrze może być to zwykły quad z okrągłą teksturą. Jeśli chcemy aby quad miał okrągłą teksturę, najpierw musimy jakąś stworzyć, a następnie ją zaimportować (co można dokonać przeciągając ją do okna z assetami), a gdy już znajdzie się ona w plikach naszej gry, musimy kliknąć na nasz prefab i przeciągnąć nową teksturę na przycisk „Add Component”. Jeśli tekstura przedstawia koło, to oznacza, że korzysta z przeźroczystości, a podstawowy shader w Unity nie obsługuje przeźroczystości. Aby uporać się z tym problemem trzeba zmienić shader na jakiś inny. Jeśli utworzymy na scenie instancję tego prefabu, to w trakcie zmieniania shadera od razu będziemy widzieli jak zmienia się nasza tekstura, więc można szybko znaleźć odpowiadający nam shader. I teraz wracamy do funkcji tworzącej żeton, aby dodać w niej następującą linijkę kodu:

 Token [x, y] = Instantiate (Resources.Load ("PreToken")) as GameObject;

Linijka ta oznacza, że do tablicy zmiennych o nazwie Token przypisujemy instancję prefabu. Mimo iż prefab ma wiele wspólnego z typem GameObject, to nie jest traktowany tak samo, dlatego musimy dodać wyrażenie „as GameObject”, aby móc go przypisać do zmiennej, która nie jest prefabem. Dotychczas nie zadeklarowaliśmy jeszcze zmiennej Token,więc musimy ją zadeklarować:

 public GameObject [,] Token = new GameObject [6, 6];

Dzięki przypisaniu instancji do zmiennej będziemy mogli łatwo odwołać się do niej w przyszłości. Chcemy jeszcze, aby ta instancja pojawiła się tuż nad polem, co można dokonać następującymi linijkami kodu:

 Token [x, y].transform.parent = Field [x, y].transform;
 Token [x, y].transform.localPosition = new Vector3 (0, 0, -0.1f);

W ten sposób pole stanie się ojcem żetonu i pojawi się minimalnie nad nim. Cała funkcja będzie więc wyglądała tak:

 public IEnumerator CreateToken (int x, int y) {
  Token [x, y] = Instantiate (Resources.Load ("PreToken")) as GameObject;
  Token [x, y].transform.parent = Field [x, y].transform;
  Token [x, y].transform.localPosition = new Vector3 (0, 0, -7.5f);
  yield return new WaitForSeconds (0.01f);
 }

Yield odpowiada za pauzę w działaniu funkcji. Na razie nie jest ona pożyteczna, ale przyda się później.


2 komentarze:

  1. Input.GetMouseButtonDown (0) - MagicNumber. Polecam stworzyć klasę pomocniczą z kodami przycisków.

    OdpowiedzUsuń
    Odpowiedzi
    1. Racja. Myślę że dobrym rozwiązaniem byłoby tutaj dodanie do InputManagera odpowiedniego Axisu i odwoływanie się do niego.

      Usuń