Poradniki

 
 
 
Kurs RGSS - cz. 2
(Sabikku)

Witam w drugiej części kursu na temat RGSS. Przypomnę - do tej pory nauczyliśmy się zmieniać i tworzyć sceny. Tym razem postaramy się ogarnąć wszystko na temat okienek - zarówno zwykłych, jak i pozwalający na wybór jakiejś opcji. Zaznaczę jeszcze, że żeby dobrze zrozumieć ten artykuł, najlepiej być oswojony z poprzednią częścią kursu (zamieszczoną w ostatnim numerze Gońca).

Do dzieła.

Okna!

Czego nauczymy się w tym punkcie:
 + umiejscowienie gotowego okna na wybranej scenie;
 + tworzenie własnych okien;
 + zapełnianie zawartości okna.

W tej części dowiemy się, jak tworzyć okienka, kontrolować ich zawartość i wyświetlać w danej scenie. Będzie więc prawie o tym samym, o czym we wspomnianym już tutku Areva.

Oknem jest na przykład okno wyboru w ekranie tytułowym, okno wiadomości podczas dialogów, czy okno z ilością złota w Scene_Menu.

Na początek nieco teorii na temat rodziny klas opartych o Window - czyli wszystkich okienek w grze. Całość przedstawia poniższy schemat:


Klasy Window nie znajdziemy w edytorze skryptów, bo jest to jedna z klas 'zablokowanych' przez twórców Rpg Makera. Możemy z niej jednak korzystać, a nagłówki metod/zmiennych podejrzeć w helpie programu.
Owa klasa jest rodzicem dla klasy Window_Base (jak pokazano na schemacie), który natomiast jest rodzicem dla większości pozostałych okienek. Klasy w żółtych prostokątach to te, których używamy bezpośrednio w grze (tworzymy, poruszamy, usuwamy itp.) - reszta zwiększa jedynie wygodę tworzenia skryptów. W jaki sposób? Jeśli zmienimy fragment w którejś klasie nadrzędnej, u wszystkich jej 'dzieci' owa zmiana będzie widoczna. Na przykład.
Potrzebujemy zmienić kolor oznaczony jako 'normal_color', używany przy większości napisów w grze. Czy musimy wchodzić w każdą klasę okienek z osobna i ustawiać wartości tego koloru? Nie, wystarczy jeśli edytujemy odpowiedni fragment w ich 'rodzicu', w Window_Base. Wyszukujemy def normal_color i edytujemy liczby, dzięki czemu wszystkie napisy korzystające z tego koloru we wszystkich okienkach opartych na Window_Base zmienią się wedle naszych otrzeb.
Podsumowując, każda klasa może korzystać ze wszystkich zmiennych i funkcji klas jej nadrzędnych. Funkcje pisania tekstu czy wyświetlania poziomu postaci znajdują się w Window_Base - po to, żeby we wszystkich okienkach korzystających z owych funkcji nie trzeba było powtarzać kodu.
Jeśli nie rozumiesz, to nic, możesz wrócić do tego później (np. pod koniec kursu).


Przejdźmy dalej. Przedstawię tu krótko, jak umieścić okienko na którejś ze scen. Przykładowo będzie to okienko Window_Gold (z menu, wyświetlające ilość posiadanego złota) na scenie Scene_Map (czyli podczas poruszania się po mapie). Zrobimy to w trzech prostych krokach.

1. Utworzenie obiektu kontrolującego okienko. Pamiętasz, gdzie tworzyliśmy takie rzeczy w scenie? Tak, przed rozpoczęciem pętli (czyli między def main a loop do). Znajdujemy więc w Scene_Map fragment:
def main
i wklejamy pod nim tworzenie nowego okienka, czyli:
  @moje_okienko_zlota = Window_Gold.new
Dzięki temu utworzyliśmy zmienną o nazwe moje_okienko_zlota, przechowującą obiekt klasy Window_Gold --> czyli przechowującą nasze okienko.
2. Odświeżanie obiektu w każdej ramce. Który fragment sceny był odpowiedzialny za odświeżanie co ramkę? Metoda update. Schodzimy więc w Scene_Map do
  def update
i wklejamy pod nim odświeżenie naszego okienka, czyli:
  @moje_okienko_zlota.update
W ten sposób nasze okienko będzie odświeżane; jest to potrzebne przy wielu systemach, np. wjeżdżających okienkach czy okienkach wyboru.
3. Usunięcie obiektu przy zakończeniu sceny. Schodzimy pod pętlę w metodzie main (konkretniej - pod Graphics.freeze) i wklejamy tam uruchomienie funkcji niszczącej okienko:
  @moje_okienko_zlota.dispose
Teraz nasze okienko zniknie pod koniec sceny - inaczej nawet po zmianie sceny pozostałoby na ekranie.

W ten sposób umieściliśmy okienko Window_Gold na mapie - utworzyliśmy, odświeżalismy i ostatecznie usunęliśmy.


Przejdziemy teraz do konfiguracji okienek z poziomu ich klasy.
Oto krótki schemat kodu okienek:

class Window_Nazwa < Window_Base # Dopisek '< Window_Base', oznacza, że mówimy
                                 # o podklasie Window_Base (patrz - schemat wyżej).
  def initialize
    super(x, y, szerokosc, wysokosc) # Wspolrzedne i rozmiary okna.
    self.contents = Bitmap.new(width - 32, height - 32) # Utworzenie bitmapy
                                                        # zawartosci okienka.
    refresh # Wyrysowanie zawartosci okienka. Patrz - nieco nizej.
  end

  def refresh
    self.contents.clear # Wyczyszczenie zawartosci okienka.
    (...) # Kod wypelniajacy zawartosc okienka.
  end

  (...) # Inne metody okna. Może tu być np. update (o ile w srodku umiescimy super)
        # ktory bedzie wywolywany w kazdej ramce - wszystko wedle potrzeb.
end


Spójrz na Window_Gold, Window_PlayTime czy Window_Steps. Widzisz? Wszystkie pisane są zgodnie z powyższym schematem. Oczywiście mówimy o klasach z dolnego żółtego prostokącika (wspominając wcześniejszy schemat), reszta działa nieco inaczej. Do czego przydadzą nam się tego typu okienka? Do wyświetlania wszelakich potrzebnych informacji. HUD? Dodatkowe okienko w cmsie, cbsie? Wszystko to jest do zrobienia w dwóch krokach:
1. Utworzenie nowej klasy okna opartego na Window_Base (tak jak w powyższym schemacie).
2. Zaimplementowanie okna do sceny, w której chcemy je wykorzystać (np. tak jak w trójkrokowym tutku nieco wyżej).
Brakuje nam tylko jednego. Nie wiemy, jak ma wyglądać kod metody refresh. Umieścimy w niej wszystko, co będzie pokazywać nasze okienko.


Kod wypełniania okienka najpierw czyści okienko, by potem wyrysować nań zawartość. Używać będziemy funkcji z trzech klas - z klasy naszego okienka (Window_Nazwa, o ile zdefiniowaliśmy nieco niżej), z klasy Window_Base (rodzica) i klasy Window (rodzica rodzica). Najprostsze funkcje znajdują się w Window_Base - wchodzimy więc w tą klasę i przeszukujemy wszystkie def xxx. Oto lista funkcji z krótkim opisem:
   draw_actor_graphic() - wyświetla grafikę postaci (character);
   draw_actor_name() - wyświetla imię postaci;
   draw_actor_class() - wyświetla nazwę klasy postaci;
   draw_actor_level() - wyświetla aktualny poziom postaci;
   draw_actor_state() - wyświetla status postaci;
   draw_actor_exp() - wyświetla ilość punktów doświadczenia postaci;
   draw_actor_hp() - wyświetla aktualne hp postaci;
   draw_actor_sp() - wyświetla aktualne sp postaci;
   draw_actor_parameter() - wyświetla jeden z parametrów postaci (atk itp).

W nawiasach wpisujemy odpowiednie argumenty metody. Na przykład chcę, aby w moim okienku Window_Nazwa pojawiło się imię bohatera. Wchodzę więc w Window_Base i wyszukuję definicję draw_actor_name:
  def draw_actor_name(actor, x, y)
Jeśli więc chcę wyrysować imię bohatera, muszę podać w nawiasie po kolei:
* obiekt actora, czyli bohatera - $game_actors[id_bohatera_w_bazie_danych] lub $game_party.actors[numer_bohatera_w_druzynie];
* współrzędne x i y, w których imię ma zostać wyświetlone; jeśli wpiszemy dwa zera, imię pojawi się w lewym górnym rogu okienka.
Teraz, kiedy wiemy, jak używać tej metody - użyjmy jej! Wracamy do naszego okienka Window_Nazwa, zjezdżamy do refresha i wpisujemy:
   def refresh
     self.contents.clear # Standardowe wyczyszczenie.
     draw_actor_name($game_party.actors[0], 50, 10)
   end

W tym przypadku nasze okienko wyświetlać będzie imię pierwszego bohatera z drużyny we współrzędnych (50;10).

Mamy za sobą używanie zestawu metod z Window_Base. Ale co, jeśli chcemy wypisać na ekran coś, czego nie ma w powyższych metodach? Albo, jeśli chcemy narysować jakiś obrazek? Musimy wtedy działać 'sami', operując bezpośrednio na bitmapie zawartości okienka - self.contents. Contents to zmienna klasy Window, przechowująca obiekt klasy Bitmap. Wchodzimy więc w helpa i szukamy klasy Bitmap - znajdziemy w niej listę jej metod. Interesują nas dwie:
  draw_text(x, y, szerokosc, wysokosc, tekst, wysrodkowanie) - wypisuje tekst we wspolrzednych x i y, w razie czego 'spłaszczając' do wyznaczonej szerokosci i wysokosci, umiejscowiając jednocześnie zgodnie z wyśrodkowaniem (0 - lewa, 1 - środek, 2 - prawa);
  blt(x, y, obrazek, prostokat, przezroczystosc) - wyrysowuje obrazek w podanych współrzędnych x i y, 'spłaszczając' zgodnie z podanym prostokątem (obiekt klasy Rect) i przezroczystością (z zakresu 0-255);
font - obiekt klasy Font, czcionka; możemy ją łatwo modyfikować, np. font.color = Color.new(255,125,125,155).
Przykład wypisywanie tekstu:
  self.contents.draw_text(0, 0, 150, 32, 'Tekst', 0) # Wypisze tekst 'Tekst'
                                                     # w lewym górnym rogu okna.

Przykład rysowania obrazka:
  self.contents.blt(0,0, RPG::Cache.picture('ob.png'), Rect.new(0,0,100,100), 255)
      # /\ Wyrysowanie obrazka z folderu pictures w lewym gornym rogu ekranu.


I to by chyba było na tyle. Wiemy już, jak zapełniać zawartość okienek.


Na koniec tego punktu przedstawię jeszcze kilka przykładów wykorzystania zdobytej 'wiedzy'.

Przykład 1. Dodanie obrazka monety niedaleko ilości złota, w okienku ze złotem w menu (esc). Wchodzimy w Window_Gold (który obsługuje to okienko), odnajdujemy def refresh:
   def refresh
     self.contents.clear
     cx = contents.text_size($data_system.words.gold).width
     self.contents.font.color = normal_color
     self.contents.draw_text(4, 0, 120-cx-2, 32, $game_party.gold.to_s, 2)
     self.contents.font.color = system_color
     self.contents.draw_text(124-cx, 0, cx, 32, $data_system.words.gold, 2)
   end

Mamy tutaj po kolei: wyczyszczenie zawartości, kod odpowiedzialny za ustawienie koloru i wyliczenie szerokosci tekstu z iloscia zlota, wypisanie ilosci zlota, zmiana koloru, wypisanie słowa oznaczającego walutę. Musimy teraz dodać rysowanie naszego obrazka monety (umieścimy je gdziekolwiek między self.contents.clear a end). Przyjmiemy, że obrazek z monetą to mon.png i ma wymiary 32x32. Obrazek umieścimy pod walutą. Odnajdujemy więc ustawienia waluty:
   self.contents.draw_text(124-cx, 0, cx, 32, $data_system.words.gold, 2)
Współrzędne waluty to ((124-cx);0), a jej wysokość: 32. Jeśli nasz obrazek ma być równo pod walutą, wystarczy wyświetlić go w tym samym miejscu, dodając do współrzędnej y odpowiednie przesunięcie w dół (zazwyczaj za wysokość tekstu uznaje się 32):
self.contents.blt(124-cx, 32, RPG::Cache.picture('mon.png'), Rect.new(0,0,50,50), 255)
Voila. Skorzystaliśmy z metody wyrysowującej obraz w okienku. Całe refresh będzie teraz wyglądało tak:
   def refresh
     self.contents.clear
     cx = contents.text_size($data_system.words.gold).width
     self.contents.font.color = normal_color
     self.contents.draw_text(4, 0, 120-cx-2, 32, $game_party.gold.to_s, 2)
     self.contents.font.color = system_color
     self.contents.draw_text(124-cx, 0, cx, 32, $data_system.words.gold, 2)
    self.contents.blt(124-cx,32, RPG::Cache.picture('mon.png'), Rect.new(0,0,50,50), 255)
   end


Przykład 2. Utworzenie huda na mapie, w prawym gornym rogu ekranu - ma wyświetlać w trzech linijkach tekst: "Super hud!", imię bohatera i hp bohatera. Zaczynamy od utworzenia okienka. Nazwiemy je Window_MojHud. Przyjmiemy, ze hud będzie miał 200 pikseli szerokości i 128 wysokości. Według schematu na razie wygląda tak:
class Window_MojHud < Window_Base
   def initialize
     super(640-200, 0, 200, 128)
     self.contents = Bitmap.new(width - 32, height - 32)
     refresh
   end
   def refresh
     self.contents.clear
  
   end
end

Co teraz? Tak. Zawartość okienka, czyli wszystko między self.contents.clear a end. Po pierwsze, potrzebujemy tekstu "Super hud!". Wrzucimy go przy górnej krawędzi okienka, na środku:
   self.contents.draw_text(0, 0, 168, 32, 'Super hud!', 1)
Następnie mamy imię bohatera. Wypiszemy je o 32 piksele niżej niż wcześniejszy tekst (miał 32 piksele wysokości, więc nie będą miały prawa na siebie nachodzić):
   draw_actor_name($game_party.actors[0], 0, 32)
Pozostało hp gracza. Znowu wrzucamy o 32 piksele niżej:
   draw_actor_hp($game_party.actors[0], 0, 64)
Przypomnę, skąd znamy te metody. Znaleźć je można w Window_Base, gdzie umieszczono najczęściej używane funkcje tego typu (wypisane zresztą nieco wyżej w kursie). Argumenty w nawiasie poznajemy czytając definicję metody - w przypadku zarówno imienia bohatera, jak i jego hp, będzie to actor oraz współrzędne x i y.
Całość wygląda więc tak:
class Window_MojHud < Window_Base
   def initialize
     super(640-200, 0, 200, 128)
     self.contents = Bitmap.new(width - 32, height - 32)
     refresh
   end
   def refresh
     self.contents.clear
     self.contents.draw_text(0, 0, 168, 32, 'Super hud!', 1)
     draw_actor_name($game_party.actors[0], 0, 32)
     draw_actor_hp($game_party.actors[0], 0, 64)
   end
end

Ok. Okienko skończone, ale wciąż nie ma go na mapie. Wchodzimy więc do Scene_Map i wrzucamy po kolei (według trójpunktowca z kursu):
1. @moj_hudek = Window_MojHud.new -> pod def main.
2. @moj_hudek.update -> pod def update.
3. @moj_hudek.dispose -> pod Graphics.freeze.
To wszystko. Hud pojawia się na mapie, znika też kiedy trzeba. Voila.


W ten sposób kończymy punkt dotyczący okien. W razie wątpliwości czy szukania nowych możliwości - pamiętajcie, by zaglądać do definicji używanych metod (w Window_Base / helpie) oraz ewentualnie szukać i sprawdzać, jak to i owo jest zrobione w innych okienkach.


Dodatek - ukrywanie okien z pomocą przełącznika. Jest to prosty zabieg polegający na odpowiednim ustawieniu zmiennej 'visible' klasy Window. W kodzie odświeżania sceny dodajemy kod:
   @moj_hudek.visible = $game_switches[1]
$game_switches to coś na rodzaj tablicy przechowującej wszystkie przełączniki z gry. $game_switches[1] to po prostu wartość przełącznika o id 1 - true lub false.
Dzięki temu, jeśli przełącznik 1 będzie włączony, okienko stanie się widoczne, a jeśli wyłączony - niewidoczne. Jeśli chcemy zrobić to na odwrót (włączony - niewidoczne, wyłączony - widoczne), wystarczy dodać negację w wyrażeniu po prawej, czyli:
   @moj_hudek.visible = not $game_switches[1]

Okna wyboru!

Czego nauczymy się w tym punkcie:
 + umiejscowienie okna wyboru na wybranej scenie;

Wolałem rozdzielić okna na dwa punkty, bo te oparte o Window_Selectable tworzy się zupełnie inaczej niż zwykłe, oparte bezpośrednio o Window_Base. Okna wyboru to te okna, w których z pomocą klawiszy wybieramy jakieś opcje. Przykładem jest okno w ekranie sklepu, czy też okno wyboru w menu (przedmioty, ekwipunek, status itd).

Wszystkie okna wyboru wywodzą się z klasy Window_Selectable (patrz - schemat w poprzednim punkcie) - to ona kontroluje wyświetlanie i przemieszczanie kwadracika wyboru. Budując oparte na niej klasy okien możemy osiągnąć przeróżne efekty - mamy bowiem dowolną ilość kolumn, dowolnie wyrysowywane składniki itp. Jest to jednak w pewnym sensie wyższa szkoła jazdy, więc w tym kursie zajmiemy się jedynie jej podklasą - Window_Command. Window_Command odpowiada za te okna, w których do wyboru mamy jedną z kilku linijek (wspomniany ekran tytułowy, wybór akcji w walce itp). Używanie jej jest bardzo proste, jednak bez poważniejszych modyfikacji towarzyszy nam para ograniczeń:
* listę opcji wyboru podajemy tylko raz, przy tworzeniu okienka;
* opcje tworzymy jedynie w pionie (nie możemy np. zrobić dwóch kolumn).
Mamy za to jedną najważniejszą zaletę - użycie Window_Command to kwestia kilku linijek, poza tym nie musimy tworzyć osobnych podklas dla każdego używanego okienka. Przejdźmy więc do konkretów.

1. Utworzenie okienka. Wchodzimy do interesującej nas sceny, odszukujemy początek metody main i piszemy:
   @moje_okienko_wyboru = Window_Command.new(tablica_opcji, szerokosc_okna)
   @moje_okienko_wyboru.x = wspolrzedna_x
   @moje_okienko_wyboru.y = wspolrzedna_y

Tablica_opcji to tablica zawierająca zbiór napisów. Każdy string będzie osobną opcją (osobną linijką do wyboru). Możemy zapisać to np. tak: ["Opcja 1", "Opcja 2", "Opcja 3"], lub wygodniej:
   op1 = "Opcja 1"
   op2 = "Opcja 2"
   op3 = "Opcja 3"
   @moje_okienko_wyboru = Window_Command.new([op1,op2,op3], szerokosc_okna)

Szerokosc_okna to szerokosc w pikselach - zauważ, że nie podajemy tu wysokości okna, bo jest ona obliczana automatycznie (ilość opcji x 32 + 32).
Na koniec nadajemy współrzędne okienku. Czy musimy to robić? Nie, ale wtedy pojawi się dokładnie w lewym górnym rogu ekranu. Jeśli chcemy zmienić położenie okienka, musimy umiejscowić je tuż po utworzeniu.
2. Odświeżanie okienka i obsługa klawiszy. Zjeżdżamy do metody update i wklejamy nasze standardowe, okienkowe odświeżanie:
@moje_okienko_wyboru.update
Czy to wszystko? Nie, tym razem nie. Przecież chcemy, by po naciśnięciu spacji/enter wykonywała sie jakaś akcja. Przykładowo po kliknięciu spacji na Nowa Gra w ekranie tytułowym, zostaniemy przeniesieni do Scene_Map.
Na początek zrobimy warunek: if Input.trigger?(Input::C). Input.trigger? to wywołanie funkcji 'trigger?' z modułu Input. Można go podejrzeć w helpie Rpg Makera (pod hasłem 'Input'). Poza trigger? mamy też takie funkcje jak press? czy repeat?. Input::C oznacza klawisz, który trzeba nacisnąć, by warunek był spełniony. Poza C mamy jeszcze A, B, X, SHIFT, ALT, DOWN, LEFT, F5, F9 itp. Nasz kod obsługujący klawiaturę wygląda więc na razie tak:
if Input.trigger?(Input::C) and @moje_okienko_wyboru.active # <-- druga część warunku                                                          # wyjasniona będzie później.
   :(
end

Dlaczego smutna minka? No tak, nie wiemy co tam wstawić. Na pewno będzie tam kod sprawdzający, na której opcji jest aktualnie kursor (prostokącik) i zależnie od tego wykonywał jakieś akcje. Numer owej opcji (rozpoczynając od zera) zapisany jest w zmiennej index klasy Window_Selectable (rodzica naszego Window_Command). Jeśli utworzyliśmy okno z trzema opcjami - zmienna ta wynosić będzie 0, 1 lub 2. Wystarczy więc uzależnić nasz kod w taki sposób:
if Input.trigger?(Input::C) and @moje_okienko_wyboru.active
   if @moje_okienko_wyboru.index == 0
     (...) # Kod, ktory uruchomi sie po wybraniu pierwszej opcji.
   elsif @moje_okienko_wyboru.index == 1
     (...) # Kod, ktory uruchomi sie po wybraniu drugiej opcji.
   elsif @moje_okienko_wyboru.index == 2
     (...) # Kod, ktory uruchomi sie po wybraniu trzeciej opcji.
   end
end

Trochę niefajnie, kod nie wygląda czytelnie. To samo zapisać możemy w inny sposób:
if Input.trigger?(Input::C) and @moje_okienko_wyboru.active
   case @moje_okienko_wyboru.index
   when 0:
     (...) # Kod, ktory uruchomi sie po wybraniu pierwszej opcji.
   when 1:
     (...) # Kod, ktory uruchomi sie po wybraniu drugiej opcji.
   when 2:
     (...) # Kod, ktory uruchomi sie po wybraniu trzeciej opcji.
   end
end

I to by było na tyle. Ostatecznie nasz kod do umieszczenia w update sceny wyglądać będzie tak:
@moje_okienko_wyboru.update
if Input.trigger?(Input::C) and @moje_okienko_wyboru.active
   case @moje_okienko_wyboru.index
   when 0:
     (...)
   when 1:
     (...)
   when 2:
     (...)
   end
end

3. Usunięcie, zniszczenie okienka. Wskakujemy za Graphics.freeze metody main naszej sceny i dorzucamy:
   @moje_okienko_wyboru.dispose
To chyba wszystko na temat używania Window_Command w scenach. Wystarczy do większości okienek wyboru, które będziesz tworzył. Pozostałe zawiłości, jak tworzenie nowych klas w oparciu o Window_Selectable, czy dodawanie ikonek do listy wyboru - pozostawiam na później (jeśli w ogóle to przerobimy).


Dodatek - aktywowanie / dezaktywowanie okienka wyboru. Zdarza się, że potrzebujemy zablokować obsługę klawiatury w okienku. Na przykład używamy dwóch okienek wyboru i chcemy, by gracz mógł poruszać kursorem tylko jednego z nich. Służy do tego zmienna 'active' klasy Window_Selectable. Wystarczy więc ją włączyć lub wyłączyć, by kursor przestał być 'aktywny:
   @moje_okienko_wyboru.active = false
Od razu wyjaśnia sie, dlaczego obsługę klawiatury (Input.trigger?...) uzależniliśmy od tej samej zmiennej --> jeśli okno nie jest aktywne, wybranie którejś opcji spacją/enterem nie będzie możliwe. Wracając do kodu - jak łatwo się domyślić, można tu użyć również przełącznika; tak samo, jak w przypadku zmiennej 'visible'. To chyba tyle na ten temat.

 --> używanie różnych obrazków bezpośrednio na scenie;
 --> sposób działania mapy, modyfikacja tilemapy w czasie rzeczywistym;
 --> interpreter rmxp, wyszukiwanie kodu poszczególnych komend;
 --> odczytywanie danych zapisanych w bazie danych.