10. Podstawy skryptowania
ToolSet - Tutoriale
sobota, 27 lutego 2010 03:43
Nie ma co ukrywać, że bez skryptów nie stworzymy raczej rozbudowanej, obfitej w questy kampanii, powalającej gracza mnogością ścieżek i możliwości wyboru. Cóż, nawet ze skryptami nie będzie to łatwe zadanie, ale zawsze będą już jakieś podstawy :D
W tutorialu 6. Konwersacje i questy pojawił się niewielki skrypt obsługujący zadanie polegające na wybiciu wszystkich przeciwników w domku położonym gdzieś w głuszy. Ten też skrypt będzie podstawą do zaznajomienia was z procesem tworzenia skryptów w edytorze Dragon Age ToolSet.
Język skryptowy wykorzystany w Dragon Age: Początek bardzo przypomina swoją składnią C/C++, w ostateczności można go przyrównać do Java. Dlatego też osoby nieco zaznajomione z programowaniem w wyżej wymienionych językach powinny czuć się niemal jak w domu.


1. Event-driven model

Czyli (bardziej po naszemu) "model sterowania zdarzeniami". Bowiem to właśnie na eventach (zdarzenia) opiera się większość skryptów w Dragon Age: Początek. Same eventy są czymś na wzór paczek z danymi, które wysyłane są przez silnik gry w odpowiednich momentach (np.: podniesienie przedmiotu, zabicie przeciwnika, czy chociażby poruszenie się bohatera).
Każdy event posiada swój typ, cel (obiekt do którego się odnosi), oraz czas opóźnienia. Dodatkowo sporo z nich ma własne, unikatowe parametry w postaci liczb dziesiętnych, zmiennoprzecinkowych, tag-ów naszych obiektów czy po prostu tekstu.
Jako przykład weźmy zdarzenie EVENT_TYPE_ATTACK_IMPACT - event ten jest wysyłany przez silnik za każdym razem, kiedy skuteczny atak trafi w cel. Zawiera w sobie informację o postaci atakującej, jej celu, informację na temat tego czy atak był krytyczny oraz ilość zadanych obrażeń. Event zostaje wysłany do obsługującego go skryptu przypisanego do postaci atakującej. Tam z paczuszki pobierane są informacje, które przetwarzane są na konkretny efekt w grze (w tym przypadku zmniejszenie punktów życia przeciwnika). Może to wszystko wyglądać nieco skomplikowanie, jednak nie jest tak źle. Może wyjaśnię to jeszcze innymi słowami. Weźmy na przykład dwie osoby grające w jakąś bitewną planszówkę. Pomijając 90% zasad dochodzimy do momentu starcia dwóch jednostek/oddziałów. Jeden gracz rzuca kostką - załóżmy że osiągną odpowiednią liczbę na kostce, by trafić jednostkę przeciwnika. Następnie rzuca innymi kostkami, by sprawdzić jakie obrażenia zada. Powiedzmy, że w sumie wypadło 21. Nadchodzi czas na kolejny rzut - trafienie krytyczne czy nie? W naszym przykładzie okazuje się, że jednak szczęście odwróciło swe oblicze od naszego agresora i nie uzyskał on trafienia krytycznego. Kończy w tym momencie swoją turę, wysyłając event EVENT_TYPE_ATTACK_IMPACT z informacjami w stylu "zaatakowałem przeciwnika, zadając mu 21 punktów obrażeń, jednak bez trafienia krytycznego". Szczęśliwy gracz atakujący po przyjęciu eventa i zrozumieniu tego co zrobił, nakazuje teraz swojemu przeciwnikowi by zmniejszył pulę życia swojej jednostki o 21 punktów. Teraz wygląda to nieco bardziej zrozumiale? - Mam taką nadzieję :-P
Ale zostawmy już tłumaczenie i przejdźmy do sedna. Jak pamiętacie z tutoriala 6. Konwersacje i questy, ustawiliśmy wartość TEAM każdego z przeciwników na 1. Tutaj silnik gry wyręcza nas nieco, bowiem sam fakt ustawienia przeciwnikom jakiejś wartości w TEAM obliguje go (silnik) do stworzenia i wysłania eventu EVENT_TYPE_TEAM_DESTROYED zaraz po zabiciu ostatniego z przeciwników. Zdarzenie to jest o tyle proste w swej budowie, że zawiera jedynie numer ubitej właśnie drużyny.


2. Tworzenie podstawowego skryptu

Tworzenie skryptu rozpoczynamy w niemal identyczny sposób, jak tworzenie czegokolwiek innego. Klikamy zatem File->New->Script (koniecznie Script, nie mylić z Client Script):
Dragon Age ToolSet Skrypty

Jak zwykle pojawi się nasz ulubieniec, czyli Create New Resource. Jako Name wpiszmy (dla przykładu) tgw_tut_script:
Dragon Age ToolSet Skrypty

Klikamy OK i po chwili pojawi się okienko edytora skryptów:
Dragon Age ToolSet Skrypty

Jako, że nasz skrypcik uruchamiany ma być przez odpowiednie zdarzenie, należy dołączyć do niego tak zwany plik nagłówkowy, w tym wypadku events_h. Co to takiego ten plik nagłówkowy? W skrócie - jest to coś na wzór spisu treści w książce, jednak zamiast numerów stron i tytułów rozdziałów mamy spis funkcji oraz stałych.
Czym są owe stałe? W przypadku events_h jest to właśnie spis eventów. Każde zdarzenie posiada swój określony numer, jednak byłoby samobójstwem pisanie skryptów, w których posługujemy się głównie liczbami, sprawdzając do chwila która liczba co znaczy. Dlatego też twórcy Dragon Age ułatwili sobie i nam zadanie, przypisując każdemu eventowi również nazwę. I tak oto, zamiast na przykład "40" możemy wpisać "EVENT_TYPE_CLICK". Dla gry będzie to całkowicie jednoznaczne.
Wróćmy jednak do naszego pliku nagłówkowego. Aby dodać go do naszego skryptu, piszemy w pierwszej linijce (można i 63, ważne by była to pierwsza linijka skryptu - przed wszystkimi funkcjami itp.): #include "events_h". Teraz pora na główną funkcję skryptu, czyli main(). Funkcja ta jest zawsze wywoływana jako pierwsza, niezależnie od kolejności ułożenia funkcji w skrypcie.
Dopisujemy zatem do naszego skryptu:
void main()
{

    // miejsce na kod
}

W pewnym sensie mamy już działający skrypt, który poza samym istnieniem, nic jeszcze nie robi. Lecim zatem dalej. Jak pamiętacie z tutoriala o questach, sam questowy dialog zmieniał się w zależności od tak zwanych flag. Aby móc takowe zmieniać za pomocą skryptu, należy użyć funkcji WR_SetPlotFlag(). Jeśli nieco przejrzymy zawartość dokumentacji dowiemy się, iż owa funkcja jest zadeklarowana w pliku wrappers_h, zatem czym prędzej dołączamy owy plik do naszego skryptu za pomocą komendy #include (podobnie jak plik events_h).
Teraz zastanówmy się, co dokładnie chcemy zmienić? - Oczywiście flagi w dialogu. Nasz dialog posiada tag lechu_q1_clear_hut. Aby móc odnosić się do tego dialogu, musimy dołączyć go do skryptu w podobny sposób jak pliki nagłówkowe, dopisujemy zatem (pod plikami nagłówkowymi) wers #include plt_lechu_q1_clear_hut.
Po powyższych operacjach nasz skrypt powinien wyglądać mniej-więcej tak:
Dragon Age ToolSet Skrypty



3. Nieco o zmiennych

No to jedziemy dalej. Teraz dodamy do naszego skryptu zmienną. Czymże jednak jest zmienna? W skrócie - pojemnikiem przechowującym odpowiednie dane. Jednak zmienna jakiegoś typu może przechowywać tylko i wyłącznie dane tego samego typu. Bardziej obrazowo mówiąc - nie będziemy raczej przechowywać mleka w pudełku od zapałek, a notatek z wykładu w zamrażalniku :-P Tak samo w Dragon Age - każdy rodzaj zmiennej powinien przechowywać tylko i wyłącznie odpowiednie dla siebie dane. I tak oto zmienna int może przechowywać tylko liczby całkowite, zmienna float liczby zmiennoprzecinkowe, a zmienna typu string przechowa ciągli znaków (a.k.a. wyrazy lub zdania).
Pora zatem dodać do skryptu naszą zmienną, która będzie odpowiedzialna za przechowywanie aktualnie wysłanego eventu. Eventy mają swój własny typ danych, zwany event. Dodajmy zatem (już w środku funkcji main() ) taką oto linijkę:

event ev;

Co nam to dało? Otóż to, że mamy już zadeklarowaną zmienną, przechowującą zdarzenie. Jak na razie nie robi ona nic, dlatego teraz użyjemy funkcji GetCurrentEvent(), aby przypisać naszej zmiennej aktualnie wysyłany (przez obiekt, do którego przypiszemy skrypt) event. Dodajemy taką oto linijkę:

ev = GetCurrentEvent();

Proste prawda? Funkcja GetCurrentEvent() będzie teraz przy każdym wywołaniu skryptu przypisywać zmiennej ev aktualne zdarzenie.
Co dalej? Cóż, mamy już przechwycone odpowiednie informacje, jednak wciąż nie wiemy, jaki też event został przypisany w danej chwili do naszej zmiennej. Jak wspominałem, wszystkie zdarzenia mają przypisane liczby całkowite, od 0 wzwyż. W zmiennej ev znajduje się, między innymi, interesująca nas liczba. Jak ją z niej wyciągnąć? Użyć funkcji GetEventType(). Dodajemy zatem poniżej:

int nEventType; // zadeklarowanie zmiennej nEventType typu int - będzie ona przechowywać wartość liczbową eventu
nEventType = GetCurrentEvent(ev); // a oto i funkcja GetCurrentEvent() w akcji. Przypisuje ona zmiennej nEventType wartość liczbową zdarzenia, pobraną z ev (podanej tutaj jako argument - w nawiasach).

W kwestii zmiennych to by było na tyle (w przypadku tego skryptu).


4. Pętle, przełączniczki...

Teraz powiemy sobie trochę o podstawowej pętli if() oraz switch() (zwanej również przełącznikiem). Zaczniemy od przełączniczka, którego funkcja powinna wydawać się w miarę zrozumiała z samej nazwy. Na czym dokładnie to polega? Otóż podajemy naszemu przełączniczkowi jakąś zmienną, następnie przypisując każdej (interesującej nas) wartości jakieś instrukcje. Myślę, że mały pseudo-kod lepiej to zobrazuje:

switch(lampka) // switch przygląda się zmiennej (lampce)
{
     case wlaczona
: // sprawdza, czy jest włączona
     {
          poczekaj_30_sekund();
// jeśli tak, czeka 30 sekund
          wylacz(); // a następnie wyłącza lampkę
     }
     case wylaczone:
// jeśli zaś lampka jest wyłączona
     {
          wlacz();
// włącza ją
     }
}


Teraz wygląda to chyba nieco prościej - bo i takie w rzeczywistości jest. Dodamy zatem do naszego skryptu switch-a, który będzie sprawdzał jaki też event otrzymaliśmy i (jeśli event = EVENT_TYPE_TEAM_DESTROYED) wywoła odpowiednie instrukcje. Dopiszmy zatem do skryptu takie oto coś:

switch(nEventType)
{
     case EVENT_TYPE_TEAM_DESTROYED:
     {
          
// tutaj dopiszemy zaraz resztę kodu
          break;
     }
}


Mamy zatem przełączniczek obserwujący wartość nEventType i reagujący tylko na wartość EVENT_TYPE_TEAM_DESTROYED. W aktualnym stanie nic on jednak jeszcze nie robi.
Pora zatem poinformować go, że jeśli drużyna 1 padnie, ma nam zaliczyć quest-a. W tym pomoże nam pętla if() (jeżeli). Na czym polega jej działanie? W skrócie - jeśli warunek podany w nawiasach będzie prawdziwy, wywołany zostanie kod zapisany w nawiasach klamrowych pod if. Przykładowy pseudokodzik:

if(druzyna_1 == nie_zyje)
{
zalicz_quest(jozin_z_bazin);
}


Jeszcze prostsze niż switch, czyż nie? Pora zatem dodać nasze sprawdzenie przez break; w naszym przełączniczku. Dopisujemy tam coś takiego:

if(GetEventInteger(ev,0) == 1)
{
//tutaj będzie kod zaliczyjący zadanie
}


Wszystko ładnie, ale co to jest GetEventInteger()?! Już wyjaśniam - jest to funkcja pobierająca z danej eventowej paczki jedną wybraną wartość - w typ wypadku z paczki ev pobiera wartość pola z indeksem 0 (pierwszego w kolejności - numerujemy zawsze od 0 wzwyż). Pole to przechowuje wartość dopiero co zabitej drużyny. Następnie if sprawdza, czy w tym polu znajduje się wartość 1 (drużyna 1). Chyba nie jest to zbyt skomplikowane? A jak sprawdzić, co kryje się pod innymi polami - wystarczy zajrzeć na tę stronę: http://social.bioware.com/wiki/datoolset/index.php/Event_(data_type) i kliknąć interesujący was event, a następnie przyjrzeć się paragrafowi PARAMETERS.


5. Zaliczanie zadania

Pora aby nasz skrypt robił wreszcie to, do czego został stworzony - zaliczał zadanie polegające na ubiciu wszystkich potworów w chatce na bagnach. Jak to jednak zrobić? Istnieje specjalna funkcja zwana WR_SetPlotFlag(), która chętnie nas wyręczy. Jak to działa? - owa funkcja pobiera 3 argumenty. Pierwszym z nich jest dialog do którego ma się odnosić (w tym wypadku lechu_q1_clear_hut), drugi to flaga której wartość chcemy zmienić (w tym wypadku MONSTERS_SLAIN), zaś trzeci argument to wartość jaką chcemy owej fladze przypisać (tutaj będzie to TRUE - czyli prawda). Wszędzie używamy KONIECZNIE dużych liter.
Dodajmy zatem w naszym if-ie taką oto linijkę:

WR_SetPlotFlag(PLT_LECHU_Q1_CELAR_HUT, MONSTERS_SLAIN, TRUE);

Aktualny kod skryptu powinien teraz wyglądać następująco:

#include "events_h"
#include "wrappers_h"

#include "plt_lechu_q1_clear_hut"


void main()
{
     event ev;
     ev = GetCurrentEvent();
     int nEventType;
     nEventType = GetEventType(ev);

     switch(nEventType)
     {
          case EVENT_TYPE_TEAM_DESTROYED:
          {
               if(GetEventInteger(ev,0) == 1)
               {
                    WR_SetPlotFlag(PLT_LECHU_Q1_CLEAR_HUT, MONSTERS_SLAIN, TRUE);
               }
               break;
          }
     }
}

Sam skrypt jest już praktycznie gotowy do wykorzystania, jednak pozostaje jedno ale. Po podczepieniu naszego skryptu do danego obszaru zastąpimy domyślny system obsługujący wszystkie eventy. Oczywiście moglibyśmy go napisać ręcznie od podstaw, łatwiej jednak będzie użyć funkcji, która przekaże wszelkie inne eventy do owego systemu. Nazywa się ona HandleEvent(). Przyjmuje ona dwa argumenty. Pierwszy to zmienna przechowująca dane zdarzenie (ev), drugi to skrypt który ma się zając zdarzeniami (w tym wypadku reprezentowany przez stałą RESOURCE_SCRIPT_AREA_CORE).
Dodajmy zatem pod nawiasem zamykającym switch() taką oto linijkę (polecam to robić przy wszystkich skryptach podczepianych do obszaru):

HandleEvent(ev, RESOURCE_SCRIPT_AREA_CORE);

Ostatecznie zatem mamy:

#include "events_h"
#include "wrappers_h"

#include "plt_lechu_q1_clear_hut"


void main()
{
     event ev;
     ev = GetCurrentEvent();
     int nEventType;
     nEventType = GetEventType(ev);

     switch(nEventType)
     {
          case EVENT_TYPE_TEAM_DESTROYED:
          {
               if(GetEventInteger(ev,0) == 1)
               {
                    WR_SetPlotFlag(PLT_LECHU_Q1_CLEAR_HUT, MONSTERS_SLAIN, TRUE);
               }
               break;
          }
     }
    
HandleEvent(ev, RESOURCE_SCRIPT_AREA_CORE);
}


6. Podsumowanie

I to by było na tyle. Teraz pozostaje tylko podpiąć nasz świeży skrypcik do odpowiedniej lokacji z tutorial-a 6 i po robocie. Chcielibyście coś bardziej rozbudowanego i ciekawego? No cóż, czekam na propozycje skryptów - jeśli będę w stanie, chętnie je napiszę (wraz z wyjaśnieniami co i jak). Polecam jednak trochę samemu poeksperymentować. Mimo, że tutorial ten przedstawia tylko absolutne podstawy, mam nadzieję, że pomoże wam w tworzeniu czegoś bardziej rozbudowanego. W teorii będzie się to sprowadzało do dodania większej ilości sprawdzeń w switch, wywołaniu innych funkcji itp. Pełni spis funkcji znajdziecie pod tym adresem: http://social.bioware.com/wiki/datoolset/index.php/Category:Functions_from_script.ldf
Polecam także zapoznać się ze skryptami z modułu Demo, są one bowiem ładnie udokumentowane (za pomocą komentarzy), co na pewno przyda się początkującym skrypterom – czyli mi :-P
W następnym tutorialu pokażę bardziej techniczną stronę języka skryptowego Dragon Age - struktury, tablice, typy zmiennych, operatory, oraz jak tworzyć własne funkcje (zamiast tylko polegać na main() ). Tymczasem jednak - miłego skryptowania :D
    

Autor: Krossfie
Źródła: http://social.bioware.com/wiki/datoolset/index.php/Scripting_tutorial
http://social.bioware.com/wiki/datoolset/index.php/Event_(data_type)
http://social.bioware.com/wiki/datoolset/index.php/Category:Functions_from_script.ldf
http://social.bioware.com/wiki/datoolset/index.php/Category:Event_types
Komentarze (0)
Tylko zarejestrowani użytkownicy mogą pisać komentarze!
 

Online

Brak

Stwórz grę!

design3 - Learn to make games™