10 KiB
Skrypty
Silnik Piklib/BlooMoo wykonuje logikę gry interpretując skrypty tekstowe. W tym rozdziale opisana jest składnia tych skryptów, sposób w jaki silnik je wczytuje oraz kolejność inicjalizacji obiektów.
Format plików
Skrypty zapisywane są w plikach o rozszerzeniach .CNV, .DEF, .CLASS oraz .SEQ. Wszystkie mają tę samą podstawową strukturę tekstową — różnią się jedynie kontekstem użycia.
Silnik traktuje cały kod jak wielkie litery i nie rozróżnia ich wielkości. Konwencjonalnie skrypty zapisuje się wielkimi literami.
Szyfrowanie
Pliki dystrybuowane z grą są domyślnie zaszyfrowane szyfrem przestawieniowym o zmiennym przesunięciu. Plik zaszyfrowany rozpoczyna się nagłówkiem postaci:
{<X:N>}
gdzie X to litera określająca kierunek przesunięcia (D oznacza przesunięcie ujemne), a N to wartość przesunięcia. Silnik wykrywa ten nagłówek automatycznie i odszyfrowuje resztę pliku przed parsowaniem. Pliki nieszyfrowane (bez tego nagłówka) są wczytywane bezpośrednio.
Deklaracja obiektów
Obiekt zaczyna się od linii ze słowem kluczowym OBJECT:
OBJECT=NAZWA_OBIEKTU
Linie pomiędzy kolejnymi OBJECT= definiują właściwości aktualnego obiektu. Definicja obiektu trwa do końca pliku lub do napotkania kolejnej linii OBJECT=.
Jeżeli ten sam obiekt zostanie zadeklarowany ponownie w tym samym pliku, jego właściwości zostaną scalone — nowsze wpisy nadpisują wcześniejsze.
Właściwości obiektów
Właściwości zapisuje się po nazwie obiektu i znaku dwukropka:
NAZWA_OBIEKTU:WLASCIWOSC=WARTOSC
Sygnały mogą przyjmować dodatkowy parametr po znaku daszka ^:
NAZWA_OBIEKTU:ONBRUTALCHANGED^3=NAZWA_PROCEDURY
W obu przypadkach silnik akceptuje wokół znaku = zarówno brak spacji (KLUCZ=WARTOSC), jak i spacje po obu stronach (KLUCZ = WARTOSC).
Typ zmiennej
Typ jest kluczowy — bez niego silnik nie wie, jak obsłużyć obiekt, i najczęściej kończy się to wyjściem do pulpitu. Typ deklaruje się właściwością TYPE:
NAZWA_OBIEKTU:TYPE=STRING
Pełna lista dostępnych typów znajduje się w Referencji typów.
Literały i ciągi znaków
Sposób interpretacji literału zależy od kontekstu:
- Tekst w cudzysłowach (
"...") traktowany jest zawsze jako wartość typuSTRING. - Tekst bez cudzysłowów najpierw jest sprawdzany jako nazwa istniejącej zmiennej — jeżeli zmienna istnieje, używana jest jej wartość. W przeciwnym razie tekst przyjmowany jest dosłownie.
Liczby zmiennoprzecinkowe akceptują notację standardową (1.234) oraz wykładniczą z literą e lub d (1.23e4, 1.23d4).
Bloki kodu
Bloki kodu — używane jako wartość sygnału lub jako ciało procedury — zapisuje się w nawiasach klamrowych. Instrukcje rozdzielone są średnikami; ostatnia instrukcja również musi kończyć się średnikiem, w przeciwnym razie może nie zostać wykonana.
NAZWA_OBIEKTU:ONCHANGED={ZMIENNA2^PLAY("TADA");}
Cały blok kodu musi być zapisany w jednej linii — silnik nie obsługuje wieloliniowych bloków bezpośrednio w pliku skryptu.
Komentarze
Silnik rozpoznaje dwie formy komentarzy:
- Komentarz liniowy — linia zaczynająca się od znaku
#jest pomijana w całości. - Komentarz blokowy — pojedyncza instrukcja poprzedzona znakiem
!jest traktowana jako wykomentowana; obowiązuje do najbliższego średnika.
Wywoływanie metod
Metody wywołuje się przy pomocy znaku ^:
NAZWA_OBIEKTU^METODA(arg1, arg2);
Wyrażenia arytmetyczne
Wyrażenia obliczeniowe zapisuje się w nawiasach kwadratowych:
NAZWA_ZMIENNEJ^SET([NAZWA_ZMIENNEJ^GET()+"2"]);
Szczegóły operatorów i typowania znajdują się w rozdziale Arytmetyka.
Wskaźniki tekstowe
Znak * przed nazwą zmiennej lub wyrażeniem oznacza, że wartość ma zostać użyta jako nazwa innej zmiennej. Pozwala to dynamicznie odwoływać się do zmiennych skonstruowanych z tekstu:
*NAZWA_ZMIENNEJ^PLAY();
*["ANIMO_"+_I_]^PLAY();
W pierwszym przypadku NAZWA_ZMIENNEJ powinna być typu STRING i zawierać nazwę faktycznego obiektu. W drugim — nazwa obiektu konstruowana jest z wyrażenia arytmetycznego.
Argumenty procedur
Wewnątrz ciała procedury argumenty dostępne są przez znak dolara z numerem (numeracja od 1):
PROCEDURA:CODE={NAZWA_ZMIENNEJ^SET($1);}
Zmienna THIS
W bloku obsługującym sygnał dostępna jest niejawna zmienna THIS, ustawiona na referencję do obiektu, który sygnał wywołał. Zmienna jest dostępna również w procedurach zagnieżdżonych wewnątrz takiego bloku.
THIS zachowuje się nietypowo: na żądanie nazwy (GETNAME) zwraca ciąg "temp", co sugeruje, że pod spodem jest to obiekt tymczasowy. Bezpiecznie działają na niej:
- metody
GETiSETdla typów prymitywnych, - metody
SHOW,HIDE,PLAY,PAUSE,STOPiRESUMEdla obiektów graficznych (ANIMO).
Wywołanie innej metody specyficznej dla typu obiektu (np. GETCFRAMEINEVENT na ANIMO) zazwyczaj kończy się błędem silnika. Aby tego uniknąć, w skryptach AidemMedia stosowane było obejście: nazwa obiektu była najpierw zapisywana do zmiennej typu STRING, a następnie wywoływana była ^RUN(nazwa_zmiennej, nazwa_metody), która wewnętrznie rozwiązuje wskaźnik tekstowy do faktycznego obiektu.
Pętle
@LOOP
@LOOP(BEHAVIOUR code, INTEGER start, INTEGER delta, INTEGER increment)
Wykonuje code dla wartości licznika _I_ z przedziału [start, start + delta) z krokiem increment. W pseudokodzie:
for (int _I_ = start; _I_ < start + delta; _I_ += increment) {
code;
}
@FOR (BlooMoo)
@FOR(INTEGER counter, BEHAVIOUR code, INTEGER start, INTEGER delta, INTEGER increment)
Identyczna do @LOOP, z tą różnicą, że pierwszy argument wskazuje zmienną pełniącą rolę licznika zamiast domyślnej _I_.
@WHILE
@WHILE(mixed value1, STRING comparator, mixed value2, BEHAVIOUR code)
Wykonuje code tak długo, jak prawdziwy jest warunek value1 comparator value2. Listę komparatorów opisano poniżej w Instrukcji warunkowej.
Instrukcja warunkowa
Silnik udostępnia dwa warianty instrukcji @IF.
Warunek prosty
@IF(mixed value1, STRING comparator, mixed value2, BEHAVIOUR codeTrue, BEHAVIOUR codeFalse)
Dostępne komparatory:
| Komparator | Znaczenie |
|---|---|
_ |
równa się |
!_ |
różne niż |
< |
mniejsze niż |
<_ |
mniejsze lub równe |
> |
większe niż |
>_ |
większe lub równe |
Warunek złożony
@IF(STRING condition, BEHAVIOUR codeTrue, BEHAVIOUR codeFalse)
W warunku złożonym dostępne są operatory logiczne:
&&— koniunkcja (i)||— alternatywa (lub)
W warunku złożonym znak równości jest zapisywany jako apostrof (') zamiast podkreślnika (_):
| Komparator | Znaczenie |
|---|---|
' |
równa się |
!' |
różne niż |
< |
mniejsze niż |
<' |
mniejsze lub równe |
> |
większe niż |
>' |
większe lub równe |
Dynamiczne tworzenie zmiennych
Wewnątrz bloku kodu można utworzyć zmienną na bieżąco:
@INT(STRING name, INTEGER value)
@DOUBLE(STRING name, DOUBLE value)
@STRING(STRING name, STRING value)
@BOOL(STRING name, BOOL value)
Każda z instrukcji tworzy zmienną odpowiedniego typu o podanej nazwie i wartości początkowej.
Operatory skoku
Wewnątrz pętli oraz procedur można sterować przepływem instrukcjami:
@CONTINUE()— pomija pozostałe instrukcje w bieżącej iteracji pętli i przechodzi do następnej.@BREAK()— przerywa całe drzewo wywołań rozpoczęte przez bieżący sygnał lub wywołanie.@ONEBREAK()— przerywa wyłącznie bieżącą procedurę.@RETURN(mixed value)— ustawia wartość zwracaną przez procedurę, ale nie przerywa jej wykonywania.
Kolejność wczytywania skryptów
Skrypty silnika są zorganizowane hierarchicznie: skrypty z niższych poziomów hierarchii widzą zmienne swoje i wszystkich przodków, ale nie odwrotnie.
Punkt startowy
Silnik rozpoczyna od pliku Application.def w podkatalogu dane. Plik ten zawiera definicje obiektów typu APPLICATION, EPISODE oraz SCENE — pozostałe typy w tym pliku są ignorowane.
Przykładowa zawartość:
OBJECT=GAME
GAME:TYPE=APPLICATION
GAME:PATH=GAME
GAME:EPISODES=PRZYGODA
GAME:STARTWITH=PRZYGODA
OBJECT=PRZYGODA
PRZYGODA:TYPE=EPISODE
PRZYGODA:PATH=GAME\PRZYGODA
PRZYGODA:SCENES=START,CREDITS,LEBIODKA
PRZYGODA:STARTWITH=START
OBJECT=START
START:TYPE=SCENE
START:PATH=GAME\PRZYGODA\START
Ładowanie kolejnych plików
Po wczytaniu Application.def silnik ładuje plik .CNV dla każdego zdefiniowanego obiektu. Ścieżkę pliku konstruuje z atrybutu PATH obiektu (relatywnie do katalogu dane), nazwy obiektu i rozszerzenia .CNV. Jeśli plik nie istnieje, jego ładowanie jest pomijane bez błędu.
Kolejność ładowania:
- Plik powiązany z obiektem
APPLICATION. - Plik pierwszego epizodu (atrybut
STARTWITHwAPPLICATION). - Plik pierwszej sceny tego epizodu (atrybut
STARTWITHwEPISODE).
Przy lokalizowaniu plików silnik dodatkowo uwzględnia aktualnie ustawiony język (zobacz APPLICATION.SETLANGUAGE) — wybrany identyfikator języka wskazuje podkatalog z lokalizowanymi zasobami, konsultowany podczas wczytywania plików gry.
Inicjalizacja zmiennych
W ramach każdego pliku zmienne są tworzone i inicjalizowane w stałej kolejności typów:
- Procedury.
- Typy prymitywne (
STRING,DOUBLE,INTEGER,BOOL). - Tablice oraz warunki.
- Animacje, obrazy, dźwięki i fonty.
- Przyciski, pola tekstowe, sekwencje, mysz, klawiatura, obserwator kanwy.
Dla każdej zmiennej w tej fazie wywoływany jest sygnał ONINIT. Na koniec, po zakończeniu inicjalizacji wszystkich zmiennych, wywoływana jest procedura __ONINIT__, jeśli została zdefiniowana.