sobota, 5 marca 2016

Zapisujemy informacje o żetonach

Ten wpis będzie poświęcony zapisywaniu informacji o żetonach w skrypcie, co dokonamy za pomocą zmiennych.

Zmienne

Schemat deklaracji zmiennych jest prosty: najpierw piszemy typ zmiennej, a potem nazwę zmiennej. Typ zmiennej określa jaki to będzie rodzaj danych, oraz służy do deklaracji tej zmiennej, zaś nazwa zmiennej będzie pozwalała nam się do niej odwoływać. Przykładowa deklaracja zmiennej:

 int x;

Int oznacza tutaj zmienną całkowitą, zaś x jest jej nazwą i odtąd za każdym razem gdy napiszemy w programie jej nazwę, program wstawi w jej miejsce wartość zmiennej. Jeśli chcemy, możemy przypisać do zmiennej jakąś wartość już w chwili deklaracji:

 int x = 5;

W takiej sytuacji zmienna znajdująca się po lewej zostanie znaku równości zastąpiona wartością znajdującą się po prawej stronie znaku równości. Jeśli jakaś zmienna została przez nas zadeklarowana, to nie możemy jej ponownie zadeklarować, ale możemy modyfikować ją i odwoływać się do niej dowolna liczbę razy.

Zmienne automatyczne

Zmienne automatyczne są automatycznie zwalniane, gdy wyjdzie się poza blok w którym zostały zadeklarowane. W prosty sposób opisując: jeśli jakaś zmienna została zadeklarowana w nawiasie klamrowym {} lub jest jego argumentem, to zostaje zwolniona gdy wyjdzie się poza ten nawias. Przykład:


Dodatkowo zmienne deklarowane bezpośrednio w klasie trwają tak długo, jak długo trwa cały skrypt.

Tablice zmiennych

Tablice zmiennych mogą mieć dowolną liczbę wymiarów i umożliwiają zapis całej serii zmiennych. Jeśli chcemy utworzyć 10 różnych zmiennych o nazwie x, to deklarujemy zmienne w ten sposób:

 int [] x = new int [10];

Po typie zmiennej pojawia się nawias kwadratowy który oznacza deklarację tablicy, a po prawej stronie znajduje się informacja o wymiarach tablicy. Tablicę wielowymiarową deklaruje się dodając przecinki w nawiasach kwadratowych, które po lewej stronie informują ile wymiarów będzie miała tablica, a po prawej stronie oddzielają od siebie rozmiar poszczególnych wymiarów:

 int [,,] y = new int [10, 5, 4];

Podana przeze mnie tablica jest tablicą trójwymiarową o wymiarach 10x5x4, czyli zawiera w sobie łącznie 10*5*4=200 zmiennych całkowitych.
Aby odwołać się do zmiennej z tablicy musimy napisać jej nazwę, oraz nawias kwadratowy w którym podamy indeksy tej zmiennej, np.: y [3, 2, 1].
Indeksy elementów tablicy są ponumerowane od 0 do n-1, gdzie n jest rozmiarem poszczególnego wymiaru. Oznacza to, że jeśli chcemy się odwołać do pierwszego elementu tablicy x, to musimy odwołać się do zerowego indeksu, czyli x [0]. Jeśli tablica ma 10 elementów, a odwołalibyśmy się do elementu o indeksie 10, to odwołalibyśmy się do 11 elementu, czyli wyszlibyśmy poza tablice, więc trzeba zwracać uwagę na to, do jakich elementów się odwołuje.

Zmienne jako funkcje

Jeśli wielokrotnie korzystamy z jakiegoś wzoru matematycznego, warto jest go opakować w funkcje. W poprzednim wpisie omówiłem funkcje typu void, które nie zwracają żadnej wartości, ale prócz nich możemy tworzyć funkcje o typie dowolnej zmiennej, która zwróci swoją wartość w miejscu jej wywołania. Przykład funkcji typu float:

 float PitagorasKwadrat (float x, float y) {
  return x * x + y * y;
 }

Zmienne x i y są tutaj argumentami funkcji, które trzeba podawać w trakcie wywoływania tej funkcji. Przykładowe wywołanie:

 PitagorasKwadrat (WymiarPierwszegoBoku, WymiarDrugiegoBoku + 3)

Po takim wywołaniu uruchomi się nasza funkcja, która za zmienną x podstawi wartość zmiennej WymiarPierwszegoBoku, a za zmienną y podstawi wartość wyrażenia (WymiarDrugiegoBoku + 3), a w miejsce całej funkcji zostanie wstawiona wartość występująca po wyrazie "return". Takie wywołanie funkcji jest wygodniejsze, niż napisanie takiego wyrażenia:

WymiarPierwszegoBoku * WymiarPierwszegoBoku + (WymiarDrugiegoBoku + 3) * (WymiarDrugiegoBoku + 3)

Struct

Struct jest czymś co może zawierać dowolną liczbę zmiennych i funkcji i właściwie można ją traktować jako zmienną. Structy przydają się jeśli chcemy uporządkować nasze dane, a także umożliwiają tworzenie nietypowych struktur danych. Przykładowa struct:

 struct DaneOsoby {
  string Imie;
  string Nazwisko;
  int Wzrost;
  int Waga;
 }

String jest zmienną typu ciąg znaków, czyli służy do zapisywania tekstów. Structy deklaruje się w taki sam sposób jak inne zmienne:

 DaneOsoby [] NoweOsoby = new DaneOsoby [100];

Jeśli chcemy się odwołać do jakiegoś elementu znajdującego się w zmiennej typu struct, to najpierw musimy podać nazwę zmiennej, a po kropce napisać nazwę elementu do którego chcemy się odwołać. Przykład zmiany wartości takiego elementu:

 NoweOsoby[0].Imie = "Jan";

Zmienne publiczne

Jeśli chcemy, aby możliwy był dostęp do zmiennej spoza jej skryptu, to musimy oznaczyć ją jako zmienną publiczną. Robi się to w trakcie jej deklaracji, poprzez dodanie słowa "public" przed typem zmiennej, np.:

 public int x;

Dodatkowo klasowe zmienne publiczne są wyświetlane w inspektorze Unity, czyli można je modyfikować poza kodem oraz w trakcie trwania gry. Wyjątkiem są tutaj zmienne, które trudno przedstawić w inspektorze, czyli np. zmienne znajdujące się w tablicy dwuwymiarowej. Warto też pamiętać, że jeśli zwiększamy uprawnienia jakiegoś elementu, to musimy też zwiększyć uprawnienia elementów do których można się odwołać w tym elemencie, czyli przykładowo jeśli tworzymy structa publicznego, to jego elementy też muszą być publiczne.

Tworzenie sposobu zapisu danych żetonów

No i teraz powrót do mojego projektu: zapiszemy w nim dane żetonów. Podstawowe informacje o żetonach będą wyświetlane na kartach i będą zawierać 4 podstawowe informacje:
- Wartość żetonu,
- Typ żetonu,
- Typ umiejętności,
- Obszar działania umiejętności.
Dane te zapiszemy w formie structu, aby można je było w łatwy sposób powielać:

 public struct BasicCard {
  public int TokenValue;
  public int TokenType;
  public int AbilityType;
  public int AbilityArea;
 }

Wiele różnych kart może mieć ten sam typ żetonów, typ umiejętności lub obszar działania umiejętności, więc zapisałem te dane pod postacią zmiennych całkowitych. Dzięki temu nie będę musiał powielać tych samych danych i będę mógł po prostu się do nich odwołać.

Teraz zajmijmy się obszarem działania umiejętności. Różne umiejętności mogą wpływać na różną ilość pól, przeważnie będą wpływać na 2, 4 lub 8 pól. Wygląda więc na to, że dla każdego obszaru warto byłoby przypisać inną liczbę pamięci. Rozgrywka do gry będzie odbywała się na dwuwymiarowej planszy, a więc i pola zasięgu umiejętności muszą być opisywane za pomocą 2 wymiarów:

 public struct AbilityPosition {
  public int x;
  public int y;
 }

Zmienna x będzie tutaj oznaczała odległość pola od żetonu na osi X i w analogiczny sposób będzie działała druga zmienna. Skoro już opracowaliśmy sposób zapisu poszczególnych pól obszaru działania umiejętności, to możemy stworzyć sposób zapisu całego obszaru umiejętności:

 public struct BasicAbility {
  public int AbilitySize;
  public AbilityPosition [] Pos;
 }

Struct ten zawiera 2 informacje: na ile pól będzie wpływał, oraz jak te pola są rozmieszczone. Dla każdego obszaru umiejętności możemy stworzyć osobną tablicę pól.

Teraz należy zadeklarować zmienne klasowe, abyśmy mieli do nich stały dostęp:

 public BasicCard [] Card = new BasicCard [100];
 public BasicAbility [] Ability = new BasicAbility [20];

Wypełnianie informacji o żetonach

Informacje o żetonach warto jest zapisać w jakiejś funkcji typu void, aby w kodzie panował porządek. Każda karta żetonu zawiera 4 informacje, a tych kart może być sporo. Schemat jest dosyć prosty:

 void LoadCardData () {

  Card [0].TokenValue = 4;
  Card [0].TokenType = 0;
  Card [0].AbilityArea = 0;
  Card [0].AbilityType = 0;

  Card [1].TokenValue = 3;
  Card [1].TokenType = 0;
  Card [1].AbilityArea = 1;
  Card [1].AbilityType = 1;
}

Schemat ten można łatwo kopiować, ale szybko zauważamy, że za każdym razem musimy zmienić w nim do 8 wartości (4 indeksy i 4 wartości zmiennych). Aby zmniejszyć nakład pracy warto jest dodać zmienną pomocniczą i odpowiednio zmodyfikować schemat:

 void LoadCardData () {
  BasicCard TCard = new BasicCard ();

  TCard.TokenValue = 4;
  TCard.TokenType = 0;
  TCard.AbilityArea = 0;
  TCard.AbilityType = 0;
  Card [0] = TCard;

  TCard.TokenValue = 3;
  TCard.TokenType = 0;
  TCard.AbilityArea = 1;
  TCard.AbilityType = 1;
  Card [1] = TCard;
}

Gdy do jednego structa przypisujemy wartość innego structa, to przenoszona jest cała jego zawartość. Z jednej strony kod ma teraz więcej linijek, ale z drugiej strony samo powielanie danych stało się szybsze - teraz zamiast modyfikować do 8 różnych wartości musimy zmodyfikować do 5 różnych wartości. Jeśli w trakcie projektu stworzymy 100 różnych kart żetonów, to "zaoszczędzimy" 300 wartości które musielibyśmy podmieniać. Leniwy programista to wydajny programista. Dodatkowo warto zauważyć, że jeśli z jakiegoś powodu będziemy chcieli zamienić indeks żetonu, to będziemy musieli zmienić tylko 1 indeksy w kodzie, a nie 4.

No to teraz czas na zapis obszarów umiejętności, którego dokonamy w podobny sposób:

 void LoadAbilityData () {
  BasicAbility TAbility = new BasicAbility ();

  TAbility.AbilitySize = 0;
  Ability [0] = TAbility;

  TAbility.AbilitySize = 2;
  TAbility.Pos = new AbilityPosition [2];
  TAbility.Pos [0].x = -1;
  TAbility.Pos [0].y = 0;
  TAbility.Pos [1].x = 1;
  TAbility.Pos [1].y = 0;
  Ability [1] = TAbility;
  TAbility.AbilitySize = 2;
 }

W tym kodzie dla każdego obszaru umiejętności możemy zadeklarować zupełnie inny rozmiar tablicy pól, dzięki czemu możemy zaoszczędzić nieco pamięci komputera.

No dobra, a co robić, jeśli będziemy chcieli pobrać rozmiar obszaru umiejętności karty x? Musimy więc zastosować taką linijkę kodu:

 Ability [Card [x].AbilityArea].AbilitySize

Nie da się jednak ukryć, że taki jest dosyć złożony, a częste używanie go w kodzie będzie niewygodne. Dlatego warto jest go opakować w funkcję:

 public int AbilitySize (int CardNumber) {
  return Ability [Card [CardNumber].AbilityArea].AbilitySize;
 }

Dzięki temu pożądaną przez nas wartość można uzyskać wpisując taką linijkę kodu:

 AbilitySize (x)

Zdecydowanie wygodniejsze. W podobny sposób można postąpić z danymi pól:

 public int AbilityX (int CardNumber, int Number) {
  return Ability [Card [CardNumber].AbilityArea].Pos[Number].x;
 }
 public int AbilityY (int CardNumber, int Number) {
  return Ability [Card [CardNumber].AbilityArea].Pos[Number].y;
 }

Brak komentarzy:

Prześlij komentarz