Strona główna
Witaj, nieznajomy!

Liczniki (timery)

W tym tutorialu przedstawię jak tworzyć misje na czas oraz jak stworzyć paski stanu informujące o np. stanu zdrowia danego aktora.

Licznik (timer)

/public/images/files/medium/misja_na_czas2 W GTA nieraz spotyka się misje, w których czas odgrywa kluczową rolę. Pokażę Wam, jak przy pomocy skryptu SCM uruchomić odliczanie w dół lub w górę.

Najpierw musimy określić długość czasu, który chcemy użyć w naszym liczniku. Robimy to przez zwykłe przypisanie wartości:


Kod:
$6011 = 30000

Czas podajemy w milisekundach (1 sekunda = 1000 milisekund). W naszym przykładzie ustawiliśmy czas na 30 sekund.

Teraz możemy uruchomić nasz licznik:
Kod:
014E: set_timer_to $6011 type  1

Pierwszym parametrem powyższego opcode'u jest nasz wcześniej określony czas. Drugi parametr to typ licznika:

  • type 1 - odliczanie w dół (3, 2, 1, 0)
  • type 0 - odliczanie w górę (0, 1, 2, 3)


Alternatywnym sposobem na uruchomienie licznika jest użycie podobnego opcodu z trzecim parametrem w postaci wpisu z GXT:
Kod:
03C3: set_timer_to $6011 type 1 GXT 'BB_19'

Tekst BB_19 w oryginalnym pliku GXT to "Time".

Stworzyliśmy licznik, to teraz musimy go odpowiednio obsłużyć. Kiedy odliczanie się skończy, zmienna przechowująca czas się wyzeruje, a sprawdzenie tego jest banalnie proste:
Kod:
:sprawdzenie
wait 
0
if
 
$6011 == 0
jf 
@sprawdzenie

014F: stop_timer $6011
// dalsze instrukcje


Tworzymy pętlę sprawdzającą, a w przypadku zakończenia odliczenia, zatrzymujemy licznik, aby nie modyfikował już naszej zmiennej.
Dobrym pomysłem byłoby zatrzymanie licznika również w przypadku śmierci gracza lub przy innym niepowodzeniu. Nie chcemy przecież, aby licznik był aktywny poza misją.


Na potrzeby swojej misji, możliwe, że będziesz chciał zatrzymać licznik w środku odliczania, a później je wznowić. Nie rób tego opcodem 014f, jak w poprzednim kroku. Zamiast tego, skorzystaj z poniższej funkcji:
Kod:
0396: pause_timer 1


Jeśli chcesz wznowić odliczanie, zmień parametr na wartość 0:
Kod:
0396: pause_timer 0


To wszystko, co potrzebujesz wiedzieć o tworzeniu liczników. Polecam pobranie przykładowej misji razem z kodem źródłowym, aby zobaczyć takie odliczanie akcji i lepiej zrozumieć praktyczne zastosowanie:

Pobierz przykład w CLEO


Pasek stanu (status text)

/public/images/files/medium/mini_mission2 Z pewnością pamiętacie misje, kiedy do pomocy mieliśmy kompana, albo kiedy musieliśmy kogoś chronić. Wtedy najczęściej pojawiały się paski stanu (ang. status text) informujące o stanie zdrowia danej postaci. Tworzy się je podobnie jak liczniki, z jedną różnicą - sami musimy aktualizować wartość podawaną do funkcji tworzącej pasek stanu.

Zacznijmy od ustawienia wartości paska stanu, co może nie być takie oczywsite przy niestandardowych ustawieniach.

Pasek stanu przyjmuje wartości od 0 do 100. Każda wartość powyżej 100 wypełni pasek do pełna. Domyślnym stanem zdrowia nowo utworzonych aktorów jest 100, ale w przypadku większych/mniejszych wartości, musimy wykonać kilka operacji matematycznych.

Kod:
0376: 1@ = create_random_actor_at -1576.88 55.26 8.57 
0223: set_actor 1@ health_to 2000


Utworzyliśmy aktora o losowym modelu (nie wymaga on ładowania, skrypt robi to za nas) i stanie zdrowia równym 2000, czyli dwudziestokrotnie większym, niż domyślnie. Aby uzyskać odpowiednie wartości, najprościej będzie podzielić stan zdrowia przez wartość początkową, a następnie pomnożyć przez 100.

Jednak zanim to zrobimy, wymagana jest jeszcze jedna operacja. Nie może być przecież tak prosto. Stan zdrowia aktorów oraz pojazdów jest podawany w liczbach całkowitych (integer), a wyniki uzyskane z dzielenia mogą być w postaci liczby zmiennoprzecinkowej (float). Dlatego dobrym pomysłem byłoby przekonwertowanie integer'a na float'a, a po wykonaniu działań, na odwrót:

Kod:
0226: $6459 = actor 1@ health
03C4: set_status_text $6459 type 1 GXT 'ZER2_43'

:aktualizuj
wait 
0
 0226: 2@ = actor 1@ health
 0093: 2@ = integer 2@ to_float

 2@ /= 2000.0
 2@ *= 100.0

 0092: 2@ = float 2@ to_integer

 008A: $6459 = 2@
jump 
@aktualizuj



  1. Najpierw odczytaliśmy stan zdrowia aktora (zwróci to wartość początkową, ustawioną wcześniej, chyba że pomiędzy tymi operacjami, aktor miał jakiś "wypadek").
  2. Potem ustawiamy pasek stanu na wartość w podanej zmiennej. Razem z modyfikacją tej zmiennej, pasek stanu także ulegnie zmianie.

  3. Ustawiliśmy typ na 1, czyli pasek. Alternatywnie, można ustawić typ na 0, który wyświetli po prostu wartość liczbową.
  4. Następnie tworzymy pętlę z aktualizacją wartości paska stanu. W jej środku wykonujemy wcześniej wspomnianie operacje, a na koniec modyfikujemy zmienną przypisaną do paska stanu.

  5. Przypisanie musimy zrobić poprzez użycie opcodu 008A, inaczej kompilator zwróciłby błąd o wątpliwych typach zmiennych.


Dodatkowo, można zrobić efekt migania pasku stanu w przypadku, gdy zdrowie aktora osiągnie stan krytyczny.

Kod:
:miganie
wait 
0
if
 
$6459 <= 10
jf 
@miganie

059C: enable_status_text $6459 flashing 1


Jeśli chcesz wyłączyć migotanie, wystarczy zmienić flashing 1 na flashing 0. Cały kod będzie wyglądał następująco:

Kod:
0376: 1@ = create_random_actor_at -1576.88 55.26 8.57 
0223: set_actor 1@ health_to 2000

0226: $6459 = actor 1@ health
03C4: set_status_text $6459 type 0 GXT 'ZER2_43'

:aktualizuj
wait 
0
 0226: 2@ = actor 1@ health
 0093: 2@ = integer 2@ to_float

 2@ /= 2000
 2@ *= 100

 0092: 2@ = float 2@ to_integer

 008A: $6964 = 2@
jump 
@miganie

:miganie
wait 
0
if
 
$6964 <= 10
jf 
@aktualizuj

059C: enable_status_text $6964 flashing 1
jump 
@aktualizuj



Polecam pobrać przykład wykorzystania takiego paska stanu razem z kodem źródłowym, aby lepiej zrozumieć jego działanie:

Pobierz przykład


Wbudowane liczniki (timery)

W silniku skryptowym GTA istnieją wbudowane liczniki, które kryją się pod dwoma zmiennymi lokalnymi - 32@ i 33@. Obie zmienne są aktualizowane niezależnie, jeśli jedna zostanie wyzerowana, druga działa dalej. Czas naliczany w tych timerach jest wyrażany w milisekundach i działają one od początku działania skryptu.

Zastosowania:

  • Liczenie czasu od jednego momentu do drugiego. Normalnie, bez użycia tych timerów, musiałbyś najpierw odczytać aktualny czas w milisekundach, później zrobić to samo przy zakończeniu odliczania, a potem odjąć jedną wartość od drugiej i odczytać wyniki.

  • Dzięki wbudowanym licznikom, sprawa wygląda dużo prościej. Najpierw zerujesz jeden timer i w odpowiednim momencie odczytujesz jego wartość. Np.
    Kod:
    32@ = 0

    :sprawdzenie
    if
     
    0118:   actor $PLAYER_ACTOR dead
    jf 
    @sprawdzenie

    008F: 1@ = integer 32@ to_float 
    1@ /= 1000

    0AD1: show_formatted_text_highpriority "Przezyles %.4f sekund!" time 5000 1@


    Musieliśmy tylko przekonwertować liczbę całkowitą na liczbę zmiennoprzecinkową przed wykonaniem dzielenia, aby otrzymać wynik w sekundach.

  • Alternatywa dla instrukcji "wait". Instrukcja wait czasem może być niedokładna i skrypt może przejść wcześniej lub później do dalszej części kodu, wbrew naszym oczekiwaniom. Jeśli zależy ci na precyzji, najlepszym wyborem będzie skorzystanie z tych liczników.

  • Kod:
    0ACD: show_text_highpriority "Już jesteś martwy." time 2000

    32@ = 0
    :check1
    wait 
    0
    if
     
    32@ >= 2000
    jf 
    @check1

    05E2: AS_actor $PLAYER_ACTOR kill_actor 5@


  • Mierzenie czasu wykonywania misji. Wystarczy wyzerować licznik na początku misji, a na końcu odczytać jego wartość, żeby się dowiedzieć, ile czasu potrzebował gracz na ukończenie zadania. Dzięki temu można w prosty sposób prowadzić np. statystyki.



Dla zaawansowanych

W SA opcody odpowiedzialne zarówno za liczniki, jak i status texty mają jedną ogromną wadę - obsługują wyłącznie zmienne globalne. O ile w main.scm nie stanowi to większego problemu, o tyle w CLEO może powodować pewne problemy z kompatybilnością. Jest jednak pewien sposób na uruchomienie tych rzeczy, korzystając ze zmiennych lokalnych. Nie jest to jednak zbyt łatwe (chociaż po wklejeniu kodu odwołuje się do niego przez funkcję SCM, co bardzo ułatwia sprawę).

Wypada w tym miejscu dać link do oryginalnego postu w języku rosyjskim:
http://sannybuilder.com/forums/viewtopic.php?id=255

Jako że jedna funkcja SCM emuluje jeden opcode, najprościej zaprezentować je po kolei.

Na początek najlepiej wkleić pod koniec skryptu dwie funkcje, które są wywoływane przez (prawie) każdą z konkretnych funkcji sterujących timerami i status textami. Wyglądają one dość zaawansowanie, jednak w tej chwili nie ma potrzeby wyjaśniania sposobu ich działania.

Kod:
:__VarToOffset
0A9F: 1@ = current_thread_pointer
0A8E: 2@ = 1@ + 0xDC
0A8D: 2@ = read_memory 2@ size 1 virtual_protect 0
if
    
0039:   2@ == 1
then
    
0006: 1@ = 0xA48960
else
    
000A: 1@ += 0x3C
end
0012: 0@ *= 4
005A: 1@ += 0@
000E: 1@ -= 0xA49960
0AB2: ret 1 1@

:__LabelToOffset
if
    
0039:   0@ == 0
then
    
0AB2: ret 1 0
end
0A9F: 1@ = current_thread_pointer
000A: 1@ += 0x10 
0A8D: 1@ = read_memory 1@ size 4 virtual_protect 0
0062: 1@ -= 0@
000A: 1@ += 4
0AB2: ret 1 1@



Timer normalnie uruchamia się opcodem 03C3 (emulacja 014E nie jest zbyt opłacalna). Tu odpala się go linijką:

Kod:
0AB1: call_scm_func @SetTimer 3 var_number 1 type 1 GXT @Timer


Na końcu skryptu wklejamy samą funkcję:

Kod:
:SetTimer
0AB1: call_scm_func @__VarToOffset 1 0@ 0@
0AB1: call_scm_func @__LabelToOffset 1 2@ 2@
0AA6: call_method 0x44CD50 struct 0xBA1788 num_params 3 pop 0 1@ 2@ 0@
0AB2: ret 0


Parametry funkcji:

  • var_number - numer zmiennej, z której pobierany jest czas. Nie można jednak podać jej jako zmienna z @ na końcu, ale jako zwykły numerek. Po prostu, jeśli chcemy operować na czasie ze zmiennej 15@, wpisujemy tam 15, 9 jeśli czas jest w zmiennej 9@ itp.
  • type - typ licznika, patrz wyżej
  • GXT - nagłówek, w którym znajduje się nazwa wpisu GXT, który chcemy użyć (patrz wyżej). Podaje się go w formacie:
  • Kod:
    :Timer
    0900: NOP "BB_19"
    0000: NOP





Zatrzymanie timera (oryginalnie opcode 014F) odbywa się poprzez funkcję:

Kod:
0AB1: call_scm_func @StopTimer 1 var_number 1


Kod:
:StopTimer
0AB1: call_scm_func @__VarToOffset 1 0@ 0@
0AA6: call_method 0x44CE60 struct 0xBA1788 num_params 1 pop 0 0@
0AB2: ret 0


Parametry:

  • var_number - patrz wyżej


Pasek stanu (status text) też jest możliwy do emulacji na zmiennych lokalnych. Emulacja opcodu 03C4 odbywa się poprzez:

Kod:
0AB1: call_scm_func @AddStatusText 4 var_number 31 type 0 GXT @StatusText line 1


Kod:
:AddStatusText                 
0AB1: call_scm_func @__VarToOffset 1 0@ 0@
0AB1: call_scm_func @__LabelToOffset 1 2@ 2@
0AA6: call_method 0x44CDA0 struct 0xBA1788 num_params 4 pop 0 3@ 2@ 1@ 0@
0AB2: ret 0


Parametry:


  • var_number - numer zmiennej, patrz wyżej
  • type - typ licznika, patrz wyżej (1 = pasek, 0 = liczba)
  • GXT - nagłówek z tekstem GXT, patrz wyżej.
  • line - linia, w której pasek stanu ma być umieszczony - najczęściej 1.



Taki status text usuwa się (oryginalnie 0151) poprzez funkcję:

Kod:
0AB1: call_scm_func @RemoveStatusText 1 var_number 31


Kod:
:RemoveStatusText
0AB1: call_scm_func @__VarToOffset 1 0@ 0@
0AA6: call_method 0x44CE80 struct 0xBA1788 num_params 1 pop 0 0@
0AB2: ret 0



  • var_number - patrz wyżej :)



Można też wyemulować działanie opcodu 059C (akurat to znalazłem samodzielnie :) ), używając:

Kod:
0AB1: call_scm_func @FlashStatusText 2 var_number 31 bool 1


Kod:
:FlashStatusText
0AB1: call_scm_func @__VarToOffset 1 0@ 0@
0AA6: call_method 0x44CEB0 struct 0xBA1788 num_params 2 pop 0 1@ 0@
0AB2: ret 0



  • var_number - patrz wyżej
  • bool - odpowiednik ostatniego parametru opcodu 059C. Ustawienie na 1 powoduje miganie paska, 0 je zatrzymuje.


Jako że to wszystko jest dość trudne do ogarnięcia, polecam sięgnąć po przykładową misję, używającą status textu przez zmienną lokalną, do pobrania TU.
Dla ciekawych, w przykładowej paczce zamieszczone są również lekko zmodyfikowane funkcje, które używają opcodów CLEO4 (eliminują potrzebę używania funkcji @__VarToOffset i @__LabelToOffset.
Dodane przez: MakG
Posting comments to this article is disabled.
Created & Powered by MakG
Wszelkie prawa zastrzeżone