Tworzenie snow i CutScenek
I. Co to jest cutscenka?
Cutscenka jest to zaplanowane zdarzenie warunkowane właściwym skryptem.
W grze znajduje się bardzo wiele Cutscenek. Cutscenkami są sny z Irenicusem, czy choćby torturowanie
II. Jak zrobić cutscenkę?
1. Cutscenkę tworzymy jak zwykły skrypt. Możemy napisać ją w notatniku, lub w DLTCEP, ale ja polecam Infinity Script editor. Przeznaczony jest właśnie do pisania skryptów i gwarantuje łatwy dostęp do plików IDS.
2. Cutscenka jak każdy skrypt składa się z bloku:
IFTutaj będą warunki, jakie muszą być spełnioneTHENRESPONSE #100Tutaj piszemy akcje, jakie mają wykonywać różne postacieENDIII. Jak rozpocząć tworzenie cutscenki.
Najczęściej cutscenki są aktywowane poprzez odpowiednią komendę w dialogu lub innym skrypcie.
W skrypcie:
IFGlobal("cutscenka","GLOBAL",0) // Warunki konieczne do rozpoczęcia cutscenkiTHENRESPONSE #100StartCutSceneMode() // blokuje mysz, klawiaturę i powoduje zniknięcie wszystkich pasków interfejsuClearAllActions() //Wstrzymuje wszystkie akcje obecnie wykonywane (np.: atak, rzucanie czaru, chód)StartCutScene("cutscenka1") //odwołanie do określonego pliku w którym mamy cutscenkę.SetGlobal("cutscenka","GLOBAL",1) // zmiana wartości zmiennej.ENDTutaj warunkiem jest zmienna która tylko gdy jest ustawiona na 0 powoduje start cutscenki
W dialogu:
IF ~~ THEN BEGIN cutscenka1SAY ~tekst.~ IF ~~ THEN REPLY ~tekst.~ DO ~ StartCutSceneMode()ClearAllActions()StartCutScene("cutscenka1") ~ EXITENDTutaj piszemy odwołanie do konkretnego pliku wcześniej blokując mysz i klawiaturę oraz przerywając wszystkie akcje.
Najważniejsze komendy, które towarzyszą zawsze cutscenkom to:
StartCutSceneMode() // blokuje mysz, klawiaturę i powoduje zniknięcie wszystkich pasków interfejsu
ClearAllActions() //Wstrzymuje wszystkie akcje obecnie wykonywane (np.: atak, rzucanie czaru, chód)
StartCutScene("cutscenka1") //odwołanie do określonego pliku w którym mamy cutscenkę.
IV. Następnie tworzymy skrypt w którym będą wszystkie akcje budujące nasza cutscenkę.
Zamieszczam tu przykładową cutscenkę pochodzącą z nowej wersji Larshy. Pokazuje ona porwanie Larshy.
IFTrue() // wszystkie warunki są spełnioneTHENRESPONSE #100CutSceneId("kasnimf1") // cutscenkę kontroluje kasnimf1, do niej odnoszą się wszystkie akcje. Jest to komenda zaczynająca każda cutscenke. Istota ją kontrolująca powinna się znajdować na obszarze, gdzie ma się odbywać cutscenka.FaceObject("Larsha") // kasnimf1 obraca się w stronę LarshyMoveViewObject("kasnimf1",INSTANT) // Kamera natychmiast przeskakuje w miejsce gdzie znajduje się kasnimf1SmallWait(10) // To oznacza, że jest chwilowa przerwa, 1/10 sekundy, jest to zauważalne przez gracza.DisplayStringHead(Myself,~Nie obronisz się przede mną driado! Już po Tobie.~) // tekst wyświetla się nad postacią CreateVisualEffectObject("SPCMWOUI",Myself) //specjalny efekt animacjiForceSpell("Larsha",CUTSCENE_DAMAGE_1B) //kasnimf1 rzuca na Larshę zaklęcieSmallWait(1)SetGlobal("porwanie","GLOBAL",4) // zmiana wartości zmiennejSetGlobal("czas","GLOBAL",1)ActionOverride("Larsha",SetInterrupt(FALSE)) // zablokowanie możliwości interakcji z Larshą, jest to konieczne dla tej cutscenki.SmallWait(1)ActionOverride("Larsha",LeaveParty()) //Larsha odłącza się od drużyny, a dzięki zablokowaniu możliwości interakcji nie podchodzi do nas z gadką na odejście z drużyny.ActionOverride("Larsha",DropInventory()) //Larsha opuszcza na ziemie cały ekwipunekSmallWait(1)ActionOverride("Larsha",PlayDead(85)) // Teraz Larsha udaje martwąWait(3) // 3 sekundy przerwyDisplayStringHead("Larsha",~ ratuj mnie!~) // tekst wyświetla się nad Larshą ForceSpell("Larsha",DRYAD_TELEPORT) // kasnimf1 rzuca czar na larshę, który powoduje znikniecie jej z mapySmallWait(20)MoveToObject("larsha") //kasnimf1 podchodzi do larshyWait(1)DestroySelf() // kasnimf1 ulega zniszczeniu bez pozostawiania ciała ani ekwipunkuEndCutSceneMode() //zakończenie cutscenki odblokowanie klawiatury i myszki, ukazanie z powrotem pasków interfejsu.ENDZ tej cutscenki także można wybrać akcje, które zawsze występują w cutscenkach np.:
CutSceneId("kasnimf1") - w nawiasie podajemy death variable postaci, która ma kontrolować cutscenkę i do której odnoszą się wszystkie akcje bezpośrednio. Rozpoczyna każdą cutscenkę
EndCutSceneMode() - zakończenie cutscenki, konieczne, bo inaczej wciąż klawiatura i myszka pozostaną zablokowane, a my nie będziemy mogli grać dalej. Zakańcza każda cutscenkę.
W tej cutscence pojawiła się niezwykle ważna komenda
ActionOverride(O:Actor*,A:Action*) - jest ona bardzo przydatna w tworzeniu cutscenek, sprawia, że, pomimo że cutscenkę kontroluje jedna postać to może ona kazać innym wykonywać jakieś akcje.
W Actor wpisujemy death variable postaci, która ma wykonać jakąś akcję znajdującą się w action. Np.:
ActionOverride("Larsha",LeaveParty()) pomimo że Larsha nie kontroluje bezpośrednio cutscenki to dzięki ActionOverride ma nakazane, że ma się odłączyć od drużyny.
V. Dlaczego ActionOverride jest bardzo ważne i pomocne
Dzięki ActionOverride możemy umieścić całą cutscenkę w jednym bloku, co jest korzystniejsze niż umieszczać ją w kilku np.:
IFTrue() // wszystkie warunki są spełnioneTHENRESPONSE #100CutSceneId("kasnimf1") // cutscenkę kontroluje kasnimf1, do niej odnoszą się wszystkie akcje.. FaceObject("Larsha")MoveViewObject("kasnimf1",INSTANT)SmallWait(10)DisplayStringHead(Myself,~Nie obronisz się przede mną driado! Już po Tobie.~)CreateVisualEffectObject("SPCMWOUI")ForceSpell("Larsha",CUTSCENE_DAMAGE_1B)SmallWait(1)SetGlobal("porwanie","GLOBAL",4)SetGlobal("czas","GLOBAL",1).SmallWait(1)SmallWait(1)Wait(3)ForceSpell("Larsha",DRYAD_TELEPORT)SmallWait(20)MoveToObject("larsha")Wait(1)DestroySelf()EndCutSceneMode()ENDIFTrue() // wszystkie warunki są spełnioneTHENRESPONSE #100CutSceneId("Larsha") // cutscenkę kontroluje Larsha, do niej odnoszą się wszystkie akcje..SmallWait(10)SetInterrupt(FALSE)SmallWait(1)LeaveParty()DropInventory()SmallWait(1)PlayDead(85)Wait(3)DisplayStringHead("Larsha",~ ratuj mnie~) SmallWait(20)Wait(1)ENDTa cutscenka odpowiada dokładnie tamtej, tyle, że tworzymy 2 osobne bloki, a w każdym są akcje dotyczące danej postaci.
Ten typ jest zawsze bardzo niepewny, co do swojej poprawności. W tym przypadku jest poprawnie, ale bardzo łatwo się pomylić, zwłaszcza, jeśli chodzi o czas pomiędzy akcjami. W rozdzielonych blokach może się okazać, że postacie za sobą nie nadążają, bo źle rozmieściliśmy odstępy pomiędzy akcjami.
Dzięki możliwości korzystania z ActionOverride możemy połączyć wszystko w jeden blok i mamy pewność, że wszyscy zakończą swoje akcje równocześnie.
Co do akcji, jakie mogą wystąpić w cutscenkach nie będę się rozpisywał, bo do tego trzeba poznać działanie większości plików IDS. W wyżej przedstawionej cutscence zamieściłem tylko te najczęściej używane.
VI. Różne dziwne rzeczy w cutscenkach.
W cutscenkach można stosować także rozpoczęcia dialogów.
Można wtedy tworzyć ciekawe połączenia pomiędzy Cutscenkami a dialogami.
Np.:
IFGlobal("cutscenka","GLOBAL",0)THENRESPONSE #100StartCutSceneMode()ClearAllActions()SetGlobal("cutscenka","GLOBAL",1)SmallWait(10)MoveToObject(player1)StartDialogueNoSet(Player1) // rozpoczęcie dialogu z player1, czyli dzieckiem BhaalaENDTen skrypt musi być przypisany postaci, która wykona te akcje. Oczywiście można zrobić odwołanie do konkretnego pliku cutscenki, jednak przy bardzo krótkich cutscenkach można zrobić jak wyżej zamieszczając ją w skrypcie określonej postaci do której akcje się odnoszą np: chcemy żeby Larsha wykonała wszystkie akcje jakie są zamieszczone w tym skrypcie, to ten cały blok, musi się znaleźć w skrypcie, który jest jej przypisany.
Na końcu jak widać nie ma EndCutSceneMode() jest natomiast StartDialogueNoSet(Player1) który sprawia, że zaczynamy dialog z player1.
Jeśli używamy StartDialogueNoSet(Player1) to dany dialog musi być z góry przypisany postaci, która dialog rozpoczyna. Jeśli na natomiast nie jest on przypisany to należy użyć
StartDialog("dialog",Player1) - wtedy dana postać której ta akcja dotyczy rozmawia z dzieckiem Bhaala wg Dialogu „dialog” i nie musi go mieć przypisanego.
Wraz z zakończeniem dialogu cutscenka się skończy, a jeśli chcemy, żeby się kontynuowała to należy zrobić konkretne odwołanie na końcu dialogu.
IF ~~ THEN BEGIN cutscenka1SAY ~tekst.~IF ~~ THEN REPLY ~tekst.~ DO ~ StartCutSceneMode()ClearAllActions()StartCutScene("cutscenka2") ~ EXITENDW ten sposób cutscenka jest kontynuowana, jednak wprowadzenie dialogu sprawia, że musimy stworzyć nowy plik. Tzn. akcje które były przed dialogiem znajdują się w skrypcie postaci (przykładowo) a następne akcje po dialogu musza się znajdować w osobnym skrypcie (w tym przypadku "cutscenka2")
VII. Teraz będzie najważniejsze, czyli tworzenie snów.
Sen składa się z 2 podstawowych elementów, a mianowicie cutscenki i nadpisania skryptu Player1d
1. Tworzymy sobie folder gdzie będą pliki naszego moda
2. Tworzymy plik tp2 o nazwie Setup-nazwamoda.tp2 (np.: za pomocą notatnika lub po prostu zmieniając nazwę już istniejącego pliku tp2 z innego moda, wtedy kasujemy także zawartość tego pliku.
3. Bierzemy z innego moda lub ściągamy Weidu i zmieniamy jego nazwę na setup-nazwamoda.exe
4. W folderze moda za pomocą Infinity Script Editor lub notatnika tworzymy plik Player1d.baf
5. Piszemy w nim:
IFGlobal("sen","GLOBAL",0)THENRESPONSE #100SetGlobal("sen","GLOBAL",1)StartCutSceneMode()ClearAllActions()StartCutScene("cutsen")ENDi zapisujemy
6. dzięki temu możliwe będzie odwołanie do pliku gdzie będzie cutscenka
7. teraz tworzymy następny plik o nazwie (w tym przypadku) cutsen.baf
8. zamieszamy w nim przykładową cutscenke, która będzie snem
Zamieszczam tutaj fragment pierwszego snu z Irenicusem i Imoen
IFTrue()THENRESPONSE #100CutSceneId(Player1) //Player1 kontroluje cutscenkęStorePartyLocations() //zapamiętanie obecnego położenia drużynyFadeToColor([30.0],0) // zaciemnienie ekranuWait(1) //1sekunda zaciemnieniaRest() // odpoczynek dla Player1, dzięki któremu odzyskuje wszystkie czary, leczy rany itd.ActionOverride(Player2,Rest())ActionOverride(Player3,Rest())ActionOverride(Player4,Rest())ActionOverride(Player5,Rest())ActionOverride(Player6,Rest()) // odpoczynek reszty drużynyWait(1) // 2 sekunda zaciemnieniaLeaveAreaLUAPanic("AR0011","",[799.1579],0)LeaveAreaLUA("AR0011","",[799.1579],8)ActionOverride(Player2,LeaveAreaLUA("AR0011","",[715.1619],2))ActionOverride(Player3,LeaveAreaLUA("AR0011","",[733.1691],14))ActionOverride(Player4,LeaveAreaLUA("AR0011","",[801.1736],0))ActionOverride(Player5,LeaveAreaLUA("AR0011","",[700.1730],4))ActionOverride(Player6,LeaveAreaLUA("AR0011","",[651.1636],2)) // cała drużyna przenosi się na lokację gdzie ma się odbyć sen ( biblioteka w której się wychowaliśmy)MultiPlayerSync()MoveViewPoint([649.2036],INSTANT) //Natychmiastowe przesunięcie kamery do danego punkt na mapieApplySpell(Player2,SURE_SLEEP)ApplySpell(Player3,SURE_SLEEP)ApplySpell(Player4,SURE_SLEEP)ApplySpell(Player5,SURE_SLEEP)ApplySpell(Player6,SURE_SLEEP) // wszyscy oprócz player1 leżą na ziemi jakby spali.Explore() // odsłonięcie całej mapySmallWait(5)FadeFromColor([30.0],0) //koniec zaciemnienia, teraz widzimy gdzie jesteśmyMoveViewPoint([799.1579],6) //Player1 przemieszcza się do konkretnego punktuWait(3)MAŁA ZMIANA W SKRYPCIEActionOverride("Dream1",MoveToObject(Player1)) //Imoen idzie w naszą stronę........... ciąg dalszy cutscenki............EndCutSceneMode() //zakończenie cutscenkiRestParty() //dzięki temu mamy ten filmik jak śpimy pod namiotem lub gdzieś indziej.END9. Jak widać sen składa się z kilku podstawowych elementów
- sen jest cutscenką i ma wszystkie komendy, które cutscenkę charakteryzują
- w snach często pojawia się
FadeToColor([30.0],0) i FadeFromColor([30.0],0) czyli zaciemnienie i odsłonięcie ekranu, bo zazwyczaj w czasie snu gdzieś się przenosimy
- komenda odpoczynku, dzięki której nasza drużyna zapamiętuje czary i leczy rany:
Rest()ActionOverride(Player2,Rest())ActionOverride(Player3,Rest())ActionOverride(Player4,Rest())ActionOverride(Player5,Rest())ActionOverride(Player6,Rest())oraz z RestParty() dzięki któremu nasza drużyna odpoczywa i pojawia się filmik towarzyszący odpoczynkowi.
10. Mamy już cutscenkę i zapisujemy. Tworzymy także w folderze moda pod folder Backup (musi być on pusty)
11. Teraz tworzymy instalkę, czyli edytujemy plik .tp2 notatnikiem
I wpisujemy w niej
BACKUP ~nazwa folderu moda\Backup~AUTHOR ~wpisujemy siebie~BEGIN ~nazwa moda~EXTEND_TOP ~player1d.bcs~ ~nazwa folderu moda/player1d.baf~Dzięki temu nasz plik Player1d. baf zostanie skompilowany i dopisany do pliku player1d
COMPILE ~nazwa folderu moda /cutsen.baf~Teraz nasza cutscenka zostanie skompilowana.
Włączamy setup-nazwamoda.exe i instalujemy.
Właśnie stworzyliśmy sen.
VIII. Co jeśli nie chcemy aktywowac cutscenki tylko zastosować coś innego np: ustawić timer.
- często jest tak, że chcemy aby dopiero po jakimś czasie sen się pojawił, wtedy trzeba ustawić w pliku player1d takze blok go warunkujący np:
IFGlobal("czas","GLOBAL",0)THENRESPONSE #100SetGlobalTimer("sen1","GLOBAL",TWO_DAYS) //Ustawiamy timer na czas dwóch dni wewnątrz gry.SetGlobal("czas","GLOBAL",1)Rest()ActionOverride(Player2,Rest())ActionOverride(Player3,Rest())ActionOverride(Player4,Rest())ActionOverride(Player5,Rest())ActionOverride(Player6,Rest())RestParty() // dzięki temu odbędzie się także normalny odpoczynek, z przywróceniem zapamiętanych czarów, przyczym przy okazji zostanie ustawiony timer.ENDTeraz piszemy pod spodem
IFGlobal("czas","GLOBAL",1)GlobalTimerExpired("sen1","GLOBAL") // czas ustawiony w timerze minął.THENRESPONSE #100SetGlobal("czas","GLOBAL",2)StartCutSceneMode()ClearAllActions()StartCutScene("cutsen")ENDIX. Dlaczego należy nadpisywać właśnie Player1d
Ponieważ tylko dzięki nadpisaniu tego skryptu będzie możliwe pokazanie cutscenki w postaci snu, tzn. normalnie gdybyśmy włączyli grę, by sprawdzić czy nasza cutscenka działa to byśmy zobaczyli, ze nie działa, bo niby wg warunków powinna się włączyć od razu po tym jak zaczęliśmy grę.
Dzięki temu, ze napisaliśmy plik player1d to cutscenka tworząca sen uaktywni się dopiero wtedy gdy klikniemy ikonkę odpoczynku.
Myślę, że jeszcze poprawię ten tutorial, mam nadzieję, że choć trochę przybliżył wam jak tworzy się cutscenki lub sny.