Zobacz poprzedni temat :: Zobacz następny temat |
Autor |
Wiadomość |
Nazarian
Administrator
Dołączył: 01 Mar 2008
Posty: 973
Przeczytał: 0 tematów
Pomógł: 12 razy Ostrzeżeń: 0/5 Skąd: Wąchock/Rzeszów
|
Wysłany: Pią 13:48, 30 Sty 2009 Temat postu: NPC tutorial część pierwsza. |
|
Jako że dawno nie było żadnego tutorialu, postanowiłem jeden napisać, jako że mam do czynienia z niezwykle prostym NPC od banku. Do napisania parę bardzo prostych odzywek, więc na tym przykładzie mogę napisać parę słów.
Zaczniemy od szkieletu NPC, który wynika z tego, jak zbudowany jest serwer:
Kod: | -- zmienne
local focus = 0
local talk_start = 0
local talk_state = 0
function onThingMove(creature, thing, oldpos, oldstackpos)
end
function onCreatureAppear(creature)
end
function onCreatureDisappear(cid, pos)
if (focus == cid) then
focus = 0
talk_start = 0
talk_state = 0
doNpcSetCreatureFocus(0)
selfGotoIdle()
end
end
function onCreatureTurn(creature)
end
-- Rozmowa
function onCreatureSay(cid, type, msg)
msg = string.lower(msg)
if ((string.find(msg,'(%a*)witaj(%a*)') or string.find(msg,'(%a*)czesc(%a*)')) and (getDistanceTo(cid) < 4)) then
if (focus == 0) then
selfSay('')
focus = cid
doNpcSetCreatureFocus(cid)
talk_start = os.clock()
return
elseif (focus ~= cid) then
selfSay('')
return
elseif (focus == cid) then
selfSay('')
return
end
elseif ((string.find(msg,'(%a*)zegnaj(%a*)')) and (focus == cid)) then
selfSay('')
focus = 0
talk_start = 0
talk_state = 0
selfGotoIdle()
doNpcSetCreatureFocus(0)
return
elseif (focus == cid) then
if string.find(msg,'(%a*) (%a*)') then
end
end
end
function onThink()
if focus ~= 0 then
if getDistanceToCreature(focus) > 3 then
selfSay('')
focus = 0
talk_start = 0
talk_state = 0
doNpcSetCreatureFocus(0)
selfGotoIdle()
elseif (os.clock() - talk_start) > 90 then
selfSay('')
focus = 0
talk_start = 0
talk_state = 0
doNpcSetCreatureFocus(0)
selfGotoIdle()
end
end
end
|
Objaśnię go teraz po kolei. Niektóre rzeczy są tutaj, bo były w gotowych NPC i sam nie wiem, czy nie można ich najnormalniej w świecie wywalić.
Na początku deklarujemy 3 zmienne. Ci z Was, którzy nie mają do czynienia z programowaniem, może nie do końca wiedzą co to zmienna, i po co się je deklaruje.
Zmienna jest jak x w równaniu. Można pod nią coś podstawić. A deklaracja jest jak opis do zadania z treścią – że jest jakiś x w naszym zadaniu, i że coś znaczy. Tak samo jest ze zmiennymi – musimy powiedzieć komputerowi, że obiekt o takiej nazwie istnieje, żeby go później wykorzystać.
Zmienne zajmują miejsce w pamięci RAM. Jeśli ich nazwy poprzedzimy słówkiem „local” zostaną wyrzucone z pamięci zaraz po wykonaniu skryptu. Możemy postawić tam również „global” lub nic, domyślnie zmienne w LUA są globalne. Znaczy to że po zakończeniu skryptu zostanie w pamięci, i że można jej używać we wszystkich skryptach. Jednak zmiana takiej wartości w jednym powoduje, że w innych zmienna będzie miała inną wartość.
Porównując to do zadania z matmy, to jest takie x, które dla wszystkich zadań na danej lekcji jest takie samo – niezależnie czy z jego pomocą liczymy miejsca zerowe paraboli czy pochodną cząstkową.
Do czegóż mogłoby się to przydać? Np. żeby wpisać jedno Pi do pamięci, i korzystać z niego we wszystkich skryptach.
To taka mała dygresja, w 99% przypadków w skryptach korzystam z typu „local” i wystarcza. Ale jakby ktoś się uczył kiedykolwiek LUA, to powinien to wiedzieć.
No więc mamy zadeklarowane trzy zmienne:
Kod: | -- zmienne
local focus = 0
local talk_start = 0
local talk_state = 0
|
Słowa po „--” zostają zignorowane przez interpreter LUA, czyli stanowią komentarz dla programisty. Radzę często z nich korzystać, bo odejdziecie od skryptu na tydzień, i jak nie siedzicie w tym ciągle, nie będziecie wiedzieć co dany kawałek kodu robi.
Zmienna focus przechowuje unikalny numer gracza, z którym rozmawia NPC. Na początku ustawiona jest na 0, ponieważ nasz NPC z nikim nie rozmawia .
Talk_start określa moment, w którym zaczęła się rozmowa. Nazwa jest trochę myląca, bo tak naprawdę to moment, w którym gracz powiedział do npc ostatnie słowa.
Talk_state określa, w jakim stanie jest rozmowa. To się okaże później.
Dalej mamy następujący kawałek:
Kod: |
function onThingMove(creature, thing, oldpos, oldstackpos)
end
|
Jest to funkcja, która pozwala NPC zareagować w momencie, kiedy postać się ruszy. Wywoływana jest automatycznie przy poruszeniu się każdej istoty w zasięgu NPC.
Kod: |
function onCreatureAppear(creature)
end
|
Powyższą funkcję można wykorzystać jeśli chcemy aby NPC reagował jakoś na pojawienie się postaci w zasięgu.
Kod: |
function onCreatureDisappear(cid, pos)
if (focus == cid) then
focus = 0
talk_start = 0
talk_state = 0
doNpcSetCreatureFocus(0)
selfGotoIdle()
end
end
|
O ile powyższe funkcje były puste, zostawione do dalszego wykorzystania, ta wspomniana wyżej robi już coś konkretnego.
Do funkcji przekazujemy cid – unikalny numer istoty, która zniknęła z pola widzenia (nie z zasięgu, ale wylogowała się albo zeszła piętro niżej) oraz pozycję, na której to nastąpiło.
Następnie warunkiem
Kod: |
if (focus == cid) then
|
Sprawdzamy czy postać o numerze „cid” jest tą, na której „skupiony” jest NPC. Jeśli tak, i postać ta zniknęła właśnie z pola widzenia, to musimy sprawić, że NPC nie zawiesi się, przestając reagować na komendy od innych graczy.
Zerujemy zatem zmienne charakterystyczne dla danej rozmowy (czyli na kim skupia się NPC, w jakim stanie jest rozmowa, oraz ile czasu upłynęło od ostatnich słów skierowanych przez aktualnie rozmawiającego gracza do NPC.
Następnie:
Kod: |
doNpcSetCreatureFocus(0)
|
Mimo że ustawiliśmy zmienną focus na 0, dla pewności że NPC nie będzie się zawieszał, dobrze jest powyższą funkcją odwołać się bezpośrednio do pamięci silnika i „zwolnić” NPC zanim silnik postanowi sprawdzić, czy jeszcze z kimś rozmawia.
Następnie, funkcją
Sprawiamy, że NPC wróci do łażenia w kółko bez celu.
Na końcu funkcji musimy jeszcze dodać słówko „end” które informuje program, że została ona zakończona. Jest to absolutnie wymagane. Osobnym „end” musimy również zakończyć instrukcję „if (tutaj warunek) then (tutaj kod) elseif (warunek2) then (kod2) else (kod3) no i na końcu „end””. Instrukcja ta musi zawierać if, then i end. Pozostałe są opcjonalne. Kod po then wykona się tylko wtedy, kiedy warunek po if jest spełniony. Analogicznie, kod2 wykona się tylko wtedy, kiedy warunek2 jest spełniony. Kod3 wykona się, kiedy żaden z powyższych warunków nie jest spełniony.
Zostaje nam jeszcze funkcja
Kod: |
function onCreatureTurn(creature)
end
|
Dzięki której nasz NPC może reagować na to, że gracz się obrócił.
Przejdziemy teraz do ciekawszego fragmentu. Jest nim funkcja onCreatureSay(cid, type, msg). Cid to unikalny numer istoty w silniku (takiej nazwy używa się praktycznie we wszystkich skryptach, jednak nie musi się ten parametr tak wcale nazywać). Type określa typ gracza. W większości serwerów jest to pokrewne z access levelem. Ostatnim parametrem jest tekst wiadomości, który został wypowiedziany. Ze względu na poziom skomplikowania tej funkcji, pokażę po kolei jak się ją tworzy. Zaczynamy od pustego szkieletu:
Kod: |
function onCreatureSay(cid, type, msg)
end
|
Jak widać całość składa się ze słówka kluczowego „function”, które informuje komputer, że ma on do czynienia z funkcją. Nie będę się rozwodził czemu to ważne, zajmę się raczej praktyczniejszymi informacjami. Dalej mamy nazwę funkcji. Musi się ona zgadzać z tą zawartą w źródłach, więc dobrze sobie sprawdzić, czy coś takiego istnieje w kodzie źródłowym lub dokumentacji silnika. Następnie opisane już pokrótce parametry, podane w nawiasie. Taki zapis sprawia, że silnik przypisuje pod te zmienne konkretne wartości, tak jak podstawiając 666 za x w równaniu. Następnie skrypt korzysta z nich, lecz żeby wiedzieć, jak co się nazywa, musimy podać w nawiasie nazwy tych zmiennych, które wypełni za nas silnik.
Msg to dokładne słowa, wypowiedziane przez postać. Są wśród nas osoby, które lubią pisać poprawnie i rozróżniając wielkość znaków. Aby i dla nich NPC działali poprawnie, należy zamienić całość tekstu wypowiedzianego przez postać na małe litery (za każdym razem podam całość kodu, będzie widać jak ewoluuje):
Kod: |
function onCreatureSay(cid, type, msg)
msg = string.lower(msg)
end
|
Teraz, skoro już wiemy, że cała wiadomość jest napisana małymi literami, możemy poszukać w niej słowa „witaj” albo „czesc”
Kod: |
function onCreatureSay(cid, type, msg)
msg = string.lower(msg)
if (string.find(msg,'(%a*)witaj(%a*)') or (string.find(msg,'(%a*)czesc(%a*)') then
end
end
|
Funkcja “string.find(caly_tekst,’szukany tekst’)” zwraca dwie wartości – prawdę lub fałsz, w zależności od tego, czy szukany tekst został znaleziony. Jest to uproszczona zasada działania, ponieważ ta funkcja jest nieco problematyczna, ale nie będę Wam mieszał. Symbole (%a*) oznaczają, że w tym miejscu w szukanym tekście, gdzie stoi ten symbol, może wystąpić dowolna ilość dowolnych znaków. Dopiero po dodaniu takich symboli na początku i końcu szukanego tekstu funkcja zadziała poprawnie. Nie musicie się tym do końca przejmować, wystarczy pamiętać żeby takie znaczki postawić we właściwych miejscach.
Słówko „or” pomiędzy wywołaniami string.find oznacza alternatywę – „lub” z logiki. Wyrażenie takie jest prawdziwe, kiedy przynajmniej jeden z jego składników jest prawdziwy. Prawdziwe mogą być też wszystkie, nic nie stoi na przeszkodzie takiej sytuacji.
Z charakterystyki serwera wynika jeszcze jeden warunek, który musimy tu umieścić – „zasięg słuchu” NPC jest dość duży, i jeśli go nie zlimitujemy, to NPC będą odpowiadać z bardzo dużych odległości, co jest ewidentnym błędem.
Do sprawdzenia, jak daleko dana postać jest od naszego NPC służy funkcja:
Kod: |
getDistanceToCreature(cid)
|
Gdzie cid to znany nam już unikatowy numer postaci. Tutaj mała uwaga: ta funkcja działa tylko w skryptach NPC, ponieważ komp musi wiedzieć, od czego odległość ma policzyć do podanej w nawiasie istoty. W skryptach NPC jest tym punktem odniesienia sam bohater niezależny. W pozostałych skryptach korzysta się z funkcji „getDistanceBetween(pos1,pos2)”, która oblicza odległość między pozycją 1 i 2.
Korzystając z powyższej wiedzy, napiszemy co następuje:
Kod: |
function onCreatureSay(cid, type, msg)
msg = string.lower(msg)
if (getDistanceToCreature(cid) < 4) and ((string.find(msg,'(%a*)witaj(%a*)') or (string.find(msg,'(%a*)czesc(%a*)')) then
end
end
|
Zwróćcie uwagę na nowe nawiasy. Są tam niezbędne, żeby zachowana została kolejność wykonywania działań – jako że zastosujemy tutaj operator „and”. O co chodzi? Musimy mieć pewność, że gracz: (jest w promieniu kilku kratek od NPC) [color = red] i [/color] (powiedział „witaj” lub powiedział „cześć”). Trochę to zawiłe, ale niezwykle ważne. „And” oznacza po prostu „i”. W tym wypadku warunki po prawej i lewej stronie „and” muszą być spełnione, żeby spełnione było całe wyrażenie.
Warunek z odległością sprawdzamy najpierw, ponieważ jeśli będzie fałszywy, serwer nie będzie sprawdzał kolejnych warunków, od razu uzna całe wyrażenie za fałszywe i słusznie. Mówią o tym prawa algebry Boole’a albo po prostu prawidła logiki. Czym to owocuje? Mniej obliczeń serwera -> mniejsze lagi kiedy jest dużo osób. Ponadto bardziej prawdopodobne jest, że postać stoi poza bliskim zasięgiem NPC i coś mówi, niż że mówiąc coś, nie mówi „cześć” ani „witaj”. Nie musicie tego wszystkiego rozumieć, to tylko optymalizacja.
Skoro już sprawdziliśmy zadane warunki, dobrze byłoby jakoś na powitanie zareagować:
Kod: |
function onCreatureSay(cid, type, msg)
msg = string.lower(msg)
if (string.find(msg,'(%a*)witaj(%a*)') or (string.find(msg,'(%a*)czesc(%a*)') then
selfSay('Witaj' ..getCreatureName(cid).. '! Czym moge sluzyc?')
focus = cid
doNpcSetCreatureFocus(cid)
talk_start = os.clock()
return
end
end
|
Dobrze przy okazji pamiętać, że ilość enterów ani znaków tabulacji w tekście nie ma tak naprawdę znaczenia dla działania funkcji. Można zatem śmiało oddzielać od siebie części kodu pustymi liniami, oraz stosować wcięcia.
Teraz opis.
Kod: |
selfSay('Witaj' ..getCreatureName(cid).. '! Czym moge sluzyc?')
|
Powyższa funkcja sprawi, że nasz NPC powie „Witaj ...! Czym moge sluzyc?”, zamieniając ... na imię gracza, który się z nim przywitał. Imię to wybiera z bazy danych, przez silnik, funkcja getCreatureName(numer_stworzonka). Działa to również w stosunku do potworów.
Dalej ustawiamy zmienną focus na numer osoby, z którą rozmawia NPC, czyli cid, a następnie upewniamy się, że w silniku również będzie to uwzględnione. Służy do tego funkcja doNpcSetCreatureFocus(cid).
Następnie ustawiamy moment początku rozmowy na aktualny czas systemu, po czym każemy silnikowi wyjść z funkcji i nie wykonywać kolejnych linijek kodu.
No ale w szkielecie naszego NPC sprawa wygląda nieco inaczej:
Kod: |
if (focus == 0) then
selfSay(' Witaj! Czym mogę służyć?')
focus = cid
doNpcSetCreatureFocus(cid)
talk_start = os.clock()
return
elseif (focus ~= cid) then
selfSay(' Przykro mi, ale jestem zajęty. Poczekaj na swoją kolej ')
return
elseif (focus == cid) then
selfSay(' No przecież już z tobą rozmawiam ')
return
end
|
Jak widzimy zwykłe wyświetlenie wiadomości zostało tutaj zastąpione nieco bardziej skomplikowanym wrażeniem. Najpierw sprawdzamy zmienną focus. Jeśli jest równa zero, to znaczy że nasz NPC z nikim aktualnie nie rozmawia. Możemy wtedy wyświetlić wiadomość w stylu „Witaj! Czym mogę służyć?”. Później następuje elseif. Jest to konstrukcja, dzięki której wykonuje się tylko jeden z warunków. Jeśli ten po if jest prawdziwy, wykona się blok kodu po nim pomiędzy then i kolejnym elseif i program ominie wszystko aż do end. Jeśli if nie będzie prawdziwy, sprawdzone będą kolejno wszystkie elseif aż do momentu, kiedy program trafi na któryś prawdziwy (i go wykona) albo na end (i nie zrobi nic ).
No więc jeśli focus był różny od zera, czyli NPC z kimś gada, sprawdzamy kolejne warunki. Jeśli „witaj” powiedział ktoś inny niż osoba, z którą NPC aktualnie rozmawia, trzeba wyświetlić coś w stylu „Przykro mi, ale jestem zajęty. Poczekaj na swoją kolej.”
Z kolei jeśli osoba, z którą NPC rozmawia powie do niego „witaj” po raz kolejny, trzeba jej uświadomić, że już rozmawia z nią ten NPC: „No przecież już z tobą rozmawiam.”
Teraz pożegnanie:
Kod: |
elseif ((string.find(msg,'(%a*)zegnaj(%a*)')) and (focus == cid)) then
selfSay('')
focus = 0
talk_start = 0
talk_state = 0
selfGotoIdle()
doNpcSetCreatureFocus(0)
return
|
Trzeba zauważyć jedną zasadniczą rzecz: W momencie kiedy w if dochodzimy do end, kończy się wykonanie większego bloku instrukcji i program przechodzi do linijk, stojącej po end. W tym przypadku przeskakujemy do elseif związanego z pierwszym warunkiem, który sprawdzał, czy się witamy. Działa to tak, że jeśli się odezwaliśmy w jakiś sposób, to program sprawdza, czy było to powitanie, jeśli tak, to uruchamia wcześniejszy kod, a jeśli nie, to sprawdza, czy było to pożegnanie. Warunek zawiera dodatkowo jeszcze słówko ‘and’ które informuje komputer, że żeby całe elseif było prawdziwe, oba warunki po obu stronach and muszą być prawdziwe. W tym przypadku sprawdzamy, czy ktoś powiedział „żegnaj” i czy ten ktoś jest osobą, z którą NPC rozmawia. Gdyby nie sprawdzać, czy to osoba na której „skupiony” jest NPC, to wtedy niezależnie kto powiedział by w jego pobliżu „żegnaj” to NPC by się z nim żegnał, a to trochę dziwne.
To co dzieje się po sprawdzeniu warunku jest chyba oczywiste, było już wcześniej opisane – resetujemy wszystko związane z obsługą NPC, uprzednio wyświetliwszy odpowiedni komunikat.
Następnie mamy coś takiego:
Kod: |
elseif (focus == cid) then
if string.find(msg,'(%a*) (%a*)') then
end
end
|
Elseif w tym momencie znów dotyczy początkowych warunków. Został tutaj wprowadzony celowo. Jeśli gracz ani się nie żegna, ani się nie wita, ale coś mówi, to musimy sprawdzić, czy to z nim nasz NPC rozmawia. Jeśli tak, sprawdzamy dokładnie co mówi, jeśli nie, to nie interesują nas te słowa, bo zapewne nie są kierowane do NPC. W tym miejscu wstawiłem tylko jeden warunek, ale w normalnym skrypcie tutaj właśnie jest najwięcej kodu.
Po tym kawałku tekstu dajemy ‘end’ który informuje komputer, że skończyła się funkcja onCreatureSay.
Została nam rzecz ostatnia – funkcja onThink.
Kod: |
function onThink()
if focus ~= 0 then
if getDistanceToCreature(focus) > 3 then
selfSay('')
focus = 0
talk_start = 0
talk_state = 0
doNpcSetCreatureFocus(0)
selfGotoIdle()
elseif (os.clock() - talk_start) > 90 then
selfSay('')
focus = 0
talk_start = 0
talk_state = 0
doNpcSetCreatureFocus(0)
selfGotoIdle()
end
end
end
|
Ma ona to do siebie, że wykonuje się co kilka milisekund (bodajże co 60, ale nie jestem pewien), no i to również zależy od możliwości kompa hostującego serwer. Ważne jest więc, żeby w niej za bardzo nie grzebać i żeby była możliwie jak najszybsza. Dlatego tak na dobrą sprawę w większości przypadków, sprawdzany jest w niej tylko pierwszy warunek i funkcja się kończy. Chodzi oczywiście o przypadek, kiedy NPC z nikim nie rozmawia. Można wtedy darować sobie sprawdzanie, czy osoba z którą rozmawia stoi za długo bezczynnie albo za daleko odeszła. Pierwszy z warunków poniżej sprawdza właśnie dystans do postaci z którą rozmawia NPC, drugi warunek sprawdza czas.
Kolejność jest nieprzypadkowa, gdyż częściej zdarza się, że ktoś odchodzi od NPC bez pożegnania a nie stoi zbyt długo bez odzywania się. W takim wypadku bardziej prawdopodobny warunek lepiej postawić bliżej początku. Warto zawsze pamiętać o tej zasadzie, poprawa wydajności w przypadku jednego skryptu jest znikoma, ale jeśli napiszemy tak wszystkie, będzie potrzeba znacznie mniej mocy obliczeniowej na hostowanie serwera.
Ufff. To tyle co chciałem powiedzieć na teraz. Przy chwili wolnego napiszę bardziej konkretne rzeczy. Można trochę z nich wywnioskować na podstawie spisu dostępnych funkcji na forum, jednak są pewne nieduże niuanse, które wprowadziłem do nich dla poprawienia możliwości, które muszą zostać omówione.
Post został pochwalony 0 razy
Ostatnio zmieniony przez Nazarian dnia Pią 13:49, 30 Sty 2009, w całości zmieniany 1 raz
|
|
Powrót do góry |
|
|
|
|
Zły
Vip
Dołączył: 27 Sie 2008
Posty: 444
Przeczytał: 0 tematów
Pomógł: 2 razy Ostrzeżeń: 0/5 Skąd: Z Piekła
|
Wysłany: Sob 16:21, 31 Sty 2009 Temat postu: |
|
Póki co to taki pusty jakby szkielet, bez rozmów z NPCem szczegółowych, tak ?
No no, bardzo fajny ^^
Opisałeś masę rzeczy, których właściwie nie musimy wiedzieć do napisania NPC, ale się mogą kiedyś przydać
Nie jest to poradnik typu "A to zostawmy bo nie ważne co to robi" ;D
Napisałem póki co sobie w notatniku mniej więcej już coś takiego jeno teksty poprzerabiałem niektóre i z niecierpliwością czekam na więcej
Post został pochwalony 0 razy
|
|
Powrót do góry |
|
|
Nazarian
Administrator
Dołączył: 01 Mar 2008
Posty: 973
Przeczytał: 0 tematów
Pomógł: 12 razy Ostrzeżeń: 0/5 Skąd: Wąchock/Rzeszów
|
Wysłany: Sob 20:08, 31 Sty 2009 Temat postu: |
|
No spoko, dziś jeszcze spróbuje opisać niuanse handlu i wykorzystywania funkcji u NPC.
Post został pochwalony 0 razy
|
|
Powrót do góry |
|
|
|
|
Nie możesz pisać nowych tematów Nie możesz odpowiadać w tematach Nie możesz zmieniać swoich postów Nie możesz usuwać swoich postów Nie możesz głosować w ankietach
|
|